Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2da1e221 authored by Daniel Sandler's avatar Daniel Sandler Committed by Android Git Automerger
Browse files

am a5e042f9: Merge "Single finger notification expansion." into jb-dev

* commit 'a5e042f9':
  Single finger notification expansion.
parents c095eb04 a5e042f9
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -67,7 +67,7 @@
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fadingEdge="none"
            android:overScrollMode="always"
            android:overScrollMode="ifContentScrolls"
            >
            <com.android.systemui.statusbar.policy.NotificationRowLayout
                android:id="@+id/latestItems"
+3 −0
Original line number Diff line number Diff line
@@ -65,5 +65,8 @@

    <!-- Vibration duration for MultiWaveView used in SearchPanelView -->
    <integer translatable="false" name="config_search_panel_view_vibration_duration">20</integer>

    <!-- The length of the vibration when the notificaiotn pops open. -->
    <integer name="one_finger_pop_duration_ms">10</integer>
</resources>
+3 −0
Original line number Diff line number Diff line
@@ -150,4 +150,7 @@

    <!-- Height of the carrier/wifi name label -->
    <dimen name="carrier_label_height">24dp</dimen>

    <!-- The distance you can pull a notificaiton before it pops open -->
    <dimen name="one_finger_pop_limit">32dp</dimen>
</resources>
+1 −0
Original line number Diff line number Diff line
@@ -18,4 +18,5 @@
<resources>
    <item type="id" name="expandable_tag" />
    <item type="id" name="user_expanded_tag" />
    <item type="id" name="user_lock_tag" />
</resources>
+223 −51
Original line number Diff line number Diff line
@@ -22,24 +22,31 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.os.Vibrator;
import android.util.Slog;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.View.OnClickListener;

import java.util.Stack;

public class ExpandHelper implements Gefingerpoken, OnClickListener {
    public interface Callback {
        View getChildAtRawPosition(float x, float y);
        View getChildAtPosition(float x, float y);
        boolean canChildBeExpanded(View v);
        boolean setUserExpandedChild(View v, boolean userxpanded);
        boolean setUserExpandedChild(View v, boolean userExpanded);
        boolean setUserLockedChild(View v, boolean userLocked);
    }

    private static final String TAG = "ExpandHelper";
    protected static final boolean DEBUG = false;
    protected static final boolean DEBUG_SCALE = false;
    protected static final boolean DEBUG_GLOW = false;
    private static final long EXPAND_DURATION = 250;
    private static final long GLOW_DURATION = 150;

@@ -63,6 +70,9 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
    private Context mContext;

    private boolean mStretching;
    private boolean mPullingWithOneFinger;
    private boolean mWatchingForPull;
    private boolean mHasPopped;
    private View mEventSource;
    private View mCurrView;
    private View mCurrViewTopGlow;
@@ -70,7 +80,12 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
    private float mOldHeight;
    private float mNaturalHeight;
    private float mInitialTouchFocusY;
    private float mInitialTouchY;
    private float mInitialTouchSpan;
    private int mTouchSlop;
    private int mLastMotionY;
    private float mPopLimit;
    private int mPopDuration;
    private Callback mCallback;
    private ScaleGestureDetector mDetector;
    private ViewScaler mScaler;
@@ -78,6 +93,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
    private AnimatorSet mGlowAnimationSet;
    private ObjectAnimator mGlowTopAnimation;
    private ObjectAnimator mGlowBottomAnimation;
    private Vibrator mVibrator;

    private int mSmallSize;
    private int mLargeSize;
@@ -85,6 +101,8 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {

    private int mGravity;

    private View mScrollView;

    private class ViewScaler {
        View mView;

@@ -93,7 +111,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
            mView = v;
        }
        public void setHeight(float h) {
            if (DEBUG) Slog.v(TAG, "SetHeight: setting to " + h);
            if (DEBUG_SCALE) Slog.v(TAG, "SetHeight: setting to " + h);
            ViewGroup.LayoutParams lp = mView.getLayoutParams();
            lp.height = (int)h;
            mView.setLayoutParams(lp);
@@ -108,7 +126,8 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
        }
        public int getNaturalHeight(int maximum) {
            ViewGroup.LayoutParams lp = mView.getLayoutParams();
            if (DEBUG) Slog.v(TAG, "Inspecting a child of type: " + mView.getClass().getName());
            if (DEBUG_SCALE) Slog.v(TAG, "Inspecting a child of type: " +
                    mView.getClass().getName());
            int oldHeight = lp.height;
            lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
            mView.setLayoutParams(lp);
@@ -142,6 +161,8 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
        mGravity = Gravity.TOP;
        mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
        mScaleAnimation.setDuration(EXPAND_DURATION);
        mPopLimit = mContext.getResources().getDimension(R.dimen.one_finger_pop_limit);
        mPopDuration = mContext.getResources().getInteger(R.integer.one_finger_pop_duration_ms);

        AnimatorListenerAdapter glowVisibilityController = new AnimatorListenerAdapter() {
            @Override
@@ -169,38 +190,30 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
        mGlowAnimationSet.play(mGlowTopAnimation).with(mGlowBottomAnimation);
        mGlowAnimationSet.setDuration(GLOW_DURATION);

        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
        mTouchSlop = configuration.getScaledTouchSlop();

        mDetector =
                new ScaleGestureDetector(context,
                                         new ScaleGestureDetector.SimpleOnScaleGestureListener() {
            @Override
            public boolean onScaleBegin(ScaleGestureDetector detector) {
                if (DEBUG) Slog.v(TAG, "onscalebegin()");
                if (DEBUG_SCALE) Slog.v(TAG, "onscalebegin()");
                float x = detector.getFocusX();
                float y = detector.getFocusY();

                View v = null;
                if (mEventSource != null) {
                    int[] location = new int[2];
                    mEventSource.getLocationOnScreen(location);
                    x += (float) location[0];
                    y += (float) location[1];
                    v = mCallback.getChildAtRawPosition(x, y);
                } else {
                    v = mCallback.getChildAtPosition(x, y);
                }

                // your fingers have to be somewhat close to the bounds of the view in question
                mInitialTouchFocusY = detector.getFocusY();
                mInitialTouchSpan = Math.abs(detector.getCurrentSpan());
                if (DEBUG) Slog.d(TAG, "got mInitialTouchSpan: (" + mInitialTouchSpan + ")");
                if (DEBUG_SCALE) Slog.d(TAG, "got mInitialTouchSpan: (" + mInitialTouchSpan + ")");

                mStretching = initScale(v);
                mStretching = initScale(findView(x, y));
                return mStretching;
            }

            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                if (DEBUG) Slog.v(TAG, "onscale() on " + mCurrView);
                if (DEBUG_SCALE) Slog.v(TAG, "onscale() on " + mCurrView);

                // are we scaling or dragging?
                float span = Math.abs(detector.getCurrentSpan()) - mInitialTouchSpan;
@@ -210,33 +223,71 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
                drag *= mGravity == Gravity.BOTTOM ? -1f : 1f;
                float pull = Math.abs(drag) + Math.abs(span) + 1f;
                float hand = drag * Math.abs(drag) / pull + span * Math.abs(span) / pull;
                if (DEBUG) Slog.d(TAG, "current span handle is: " + hand);
                hand = hand + mOldHeight;
                float target = hand;
                if (DEBUG) Slog.d(TAG, "target is: " + target);
                hand = hand < mSmallSize ? mSmallSize : (hand > mLargeSize ? mLargeSize : hand);
                hand = hand > mNaturalHeight ? mNaturalHeight : hand;
                if (DEBUG) Slog.d(TAG, "scale continues: hand =" + hand);
                mScaler.setHeight(hand);
                float target = hand + mOldHeight;
                float newHeight = clamp(target);
                mScaler.setHeight(newHeight);

                // glow if overscale
                float stretch = (float) Math.abs((target - hand) / mMaximumStretch);
                float strength = 1f / (1f + (float) Math.pow(Math.E, -1 * ((8f * stretch) - 5f)));
                if (DEBUG) Slog.d(TAG, "stretch: " + stretch + " strength: " + strength);
                setGlow(GLOW_BASE + strength * (1f - GLOW_BASE));
                setGlow(calculateGlow(target, newHeight));
                return true;
            }

            @Override
            public void onScaleEnd(ScaleGestureDetector detector) {
                if (DEBUG) Slog.v(TAG, "onscaleend()");
                if (DEBUG_SCALE) Slog.v(TAG, "onscaleend()");
                // I guess we're alone now
                if (DEBUG) Slog.d(TAG, "scale end");
                if (DEBUG_SCALE) Slog.d(TAG, "scale end");
                finishScale(false);
                clearView();
                mStretching = false;
            }
        });
    }

    private float clamp(float target) {
        float out = target;
        out = out < mSmallSize ? mSmallSize : (out > mLargeSize ? mLargeSize : out);
        out = out > mNaturalHeight ? mNaturalHeight : out;
        return out;
    }

    private View findView(float x, float y) {
        View v = null;
        if (mEventSource != null) {
            int[] location = new int[2];
            mEventSource.getLocationOnScreen(location);
            x += (float) location[0];
            y += (float) location[1];
            v = mCallback.getChildAtRawPosition(x, y);
        } else {
            v = mCallback.getChildAtPosition(x, y);
        }
        return v;
    }

    private boolean isInside(View v, float x, float y) {
        if (DEBUG) Slog.d(TAG, "isinside (" + x + ", " + y + ")");

        if (v == null) {
            if (DEBUG) Slog.d(TAG, "isinside null subject");
            return false;
        }
        if (mEventSource != null) {
            int[] location = new int[2];
            mEventSource.getLocationOnScreen(location);
            x += (float) location[0];
            y += (float) location[1];
            if (DEBUG) Slog.d(TAG, "  to global (" + x + ", " + y + ")");
        }
        int[] location = new int[2];
        v.getLocationOnScreen(location);
        x -= (float) location[0];
        y -= (float) location[1];
        if (DEBUG) Slog.d(TAG, "  to local (" + x + ", " + y + ")");
        if (DEBUG) Slog.d(TAG, "  inside (" + v.getWidth() + ", " + v.getHeight() + ")");
        boolean inside = (x > 0f && y > 0f && x < v.getWidth() & y < v.getHeight());
        return inside;
    }

    public void setEventSource(View eventSource) {
        mEventSource = eventSource;
    }
@@ -245,10 +296,23 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
        mGravity = gravity;
    }

    public void setScrollView(View scrollView) {
        mScrollView = scrollView;
    }

    private float calculateGlow(float target, float actual) {
        // glow if overscale
        if (DEBUG_GLOW) Slog.d(TAG, "target: " + target + " actual: " + actual);
        float stretch = (float) Math.abs((target - actual) / mMaximumStretch);
        float strength = 1f / (1f + (float) Math.pow(Math.E, -1 * ((8f * stretch) - 5f)));
        if (DEBUG_GLOW) Slog.d(TAG, "stretch: " + stretch + " strength: " + strength);
        return (GLOW_BASE + strength * (1f - GLOW_BASE));
    }

    public void setGlow(float glow) {
        if (!mGlowAnimationSet.isRunning() || glow == 0f) {
            if (mGlowAnimationSet.isRunning()) {
                mGlowAnimationSet.cancel();
                mGlowAnimationSet.end();
            }
            if (mCurrViewTopGlow != null && mCurrViewBottomGlow != null) {
                if (glow == 0f || mCurrViewTopGlow.getAlpha() == 0f) {
@@ -278,23 +342,115 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (DEBUG) Slog.d(TAG, "interceptTouch: act=" + (ev.getAction()) +
                         " stretching=" + mStretching);
                         " stretching=" + mStretching +
                         " onefinger=" + mPullingWithOneFinger);
        // check for a two-finger gesture
        mDetector.onTouchEvent(ev);
        return mStretching;
        if (mStretching) {
            return true;
        } else {
            final int action = ev.getAction();
            if ((action == MotionEvent.ACTION_MOVE) && mPullingWithOneFinger) {
                return true;
            }
            if (mScrollView != null && mScrollView.getScrollY() > 0) {
                return false;
            }
            switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_MOVE: {
                if (mWatchingForPull) {
                    final int x = (int) ev.getX();
                    final int y = (int) ev.getY();
                    final int yDiff = y - mLastMotionY;
                    if (yDiff > mTouchSlop) {
                        mLastMotionY = y;
                        mPullingWithOneFinger = initScale(findView(x, y));
                        if (mPullingWithOneFinger) {
                            mInitialTouchY = mLastMotionY;
                            mHasPopped = false;
                        }
                    }
                }
                break;
            }

            case MotionEvent.ACTION_DOWN:
                mWatchingForPull = isInside(mScrollView, ev.getX(), ev.getY());
                mLastMotionY = (int) ev.getY();
                break;

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (mPullingWithOneFinger) {
                    finishScale(false);
                    clearView();
                }
                mPullingWithOneFinger = false;
                mWatchingForPull = false;
                break;
            }
            return mPullingWithOneFinger;
        }
    }

    public boolean onTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        if (DEBUG) Slog.d(TAG, "touch: act=" + (action) + " stretching=" + mStretching);
        if (DEBUG_SCALE) Slog.d(TAG, "touch: act=" + (action) +
                         " stretching=" + mStretching +
                         " onefinger=" + mPullingWithOneFinger);
        if (mStretching) {
            if (DEBUG) Slog.d(TAG, "detector ontouch");
            mDetector.onTouchEvent(ev);
        }
        switch (action) {
            case MotionEvent.ACTION_MOVE: {
                if (mPullingWithOneFinger) {
                    final float rawHeight = ev.getY() - mInitialTouchY + mOldHeight;
                    final float newHeight = clamp(rawHeight);
                    final boolean wasClosed = (mOldHeight == mSmallSize);
                    boolean isFinished = false;
                    if (rawHeight > mNaturalHeight) {
                        isFinished = true;
                    }
                    if (rawHeight < mSmallSize) {
                        isFinished = true;
                    }

                    final float pull = Math.abs(ev.getY() - mInitialTouchY);
                    if (mHasPopped || pull > mPopLimit) {
                        if (!mHasPopped) {
                            vibrate(mPopDuration);
                            mHasPopped = true;
                        }
                    }

                    if (mHasPopped) {
                        mScaler.setHeight(newHeight);
                        setGlow(GLOW_BASE);
                    } else {
                        setGlow(calculateGlow(4f * pull, 0f));
                    }

                    final int x = (int) ev.getX();
                    final int y = (int) ev.getY();
                    View underPointer = findView(x, y);
                    if (isFinished && underPointer != null && underPointer != mCurrView) {
                        finishScale(false);
                        initScale(underPointer);
                        mInitialTouchY = ev.getY();
                        mHasPopped = false;
                    }
                    return true;
                }
                break;
            }
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (DEBUG) Slog.d(TAG, "cancel");
                mStretching = false;
                if (mPullingWithOneFinger) {
                    finishScale(false);
                    mPullingWithOneFinger = false;
                }
                clearView();
                break;
        }
@@ -303,7 +459,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
    private boolean initScale(View v) {
        if (v != null) {
            if (DEBUG) Slog.d(TAG, "scale begins on view: " + v);
            mStretching = true;
            mCallback.setUserLockedChild(v, true);
            setView(v);
            setGlow(GLOW_BASE);
            mScaler.setView(v);
@@ -318,30 +474,34 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
            if (DEBUG) Slog.d(TAG, "got mOldHeight: " + mOldHeight +
                        " mNaturalHeight: " + mNaturalHeight);
            v.getParent().requestDisallowInterceptTouchEvent(true);
            return true;
        } else {
            return false;
        }
        return mStretching;
    }

    private void finishScale(boolean force) {
        float currentHeight = mScaler.getHeight();
        float targetHeight = mSmallSize;
        float h = mScaler.getHeight();
        final boolean wasClosed = (mOldHeight == mSmallSize);
        if (wasClosed) {
            h = (force || h > mSmallSize) ? mNaturalHeight : mSmallSize;
            targetHeight = (force || currentHeight > mSmallSize) ? mNaturalHeight : mSmallSize;
        } else {
            h = (force || h < mNaturalHeight) ? mSmallSize : mNaturalHeight;
            targetHeight = (force || currentHeight < mNaturalHeight) ? mSmallSize : mNaturalHeight;
        }
        if (DEBUG && mCurrView != null) mCurrView.setBackgroundColor(0);
        if (mScaleAnimation.isRunning()) {
            mScaleAnimation.cancel();
        }
        mScaleAnimation.setFloatValues(h);
        mScaleAnimation.setupStartValues();
        mScaleAnimation.start();
        mStretching = false;
        setGlow(0f);
        mCallback.setUserExpandedChild(mCurrView, h == mNaturalHeight);
        if (targetHeight != currentHeight) {
            mScaleAnimation.setFloatValues(targetHeight);
            mScaleAnimation.setupStartValues();
            mScaleAnimation.start();
        }
        mCallback.setUserLockedChild(mCurrView, false);
        if (DEBUG) Slog.d(TAG, "scale was finished on view: " + mCurrView);
        clearView();
    }

    private void clearView() {
@@ -369,6 +529,18 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener {
    public void onClick(View v) {
        initScale(v);
        finishScale(true);
        clearView();
    }

    /**
     * Triggers haptic feedback.
     */
    private synchronized void vibrate(long duration) {
        if (mVibrator == null) {
            mVibrator = (android.os.Vibrator)
                    mContext.getSystemService(Context.VIBRATOR_SERVICE);
        }
        mVibrator.vibrate(duration);
    }
}
Loading