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

Commit 91579bde authored by Selim Cinek's avatar Selim Cinek
Browse files

Convert DragDownHelper to Kotlin

Bug: 184946919
Test: build, test drag down behavior on lockscreen
Change-Id: I56eebf57a169ce6d22ad071d029135078a3b712b
parent 429e2a8b
Loading
Loading
Loading
Loading
+0 −292
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.systemui.statusbar;

import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import com.android.systemui.ExpandHelper;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.notification.row.ExpandableView;

/**
 * A utility class to enable the downward swipe on the lockscreen to go to the full shade and expand
 * the notification where the drag started.
 */
public class DragDownHelper implements Gefingerpoken {

    private static final float RUBBERBAND_FACTOR_EXPANDABLE = 0.5f;
    private static final float RUBBERBAND_FACTOR_STATIC = 0.15f;

    private static final int SPRING_BACK_ANIMATION_LENGTH_MS = 375;

    private int mMinDragDistance;
    private final FalsingManager mFalsingManager;
    private ExpandHelper.Callback mCallback;
    private float mInitialTouchX;
    private float mInitialTouchY;
    private boolean mDraggingDown;
    private final float mTouchSlop;
    private final float mSlopMultiplier;
    private DragDownCallback mDragDownCallback;
    private View mHost;
    private final int[] mTemp2 = new int[2];
    private boolean mDraggedFarEnough;
    private ExpandableView mStartingChild;
    private float mLastHeight;
    private FalsingCollector mFalsingCollector;

    public DragDownHelper(Context context, View host, ExpandHelper.Callback callback,
            DragDownCallback dragDownCallback, FalsingManager falsingManager,
            FalsingCollector falsingCollector) {
        mMinDragDistance = context.getResources().getDimensionPixelSize(
                R.dimen.keyguard_drag_down_min_distance);
        mFalsingManager = falsingManager;
        final ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = configuration.getScaledTouchSlop();
        mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
        mCallback = callback;
        mDragDownCallback = dragDownCallback;
        mHost = host;
        mFalsingCollector = falsingCollector;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                mDraggedFarEnough = false;
                mDraggingDown = false;
                mStartingChild = null;
                mInitialTouchY = y;
                mInitialTouchX = x;
                break;

            case MotionEvent.ACTION_MOVE:
                final float h = y - mInitialTouchY;
                // Adjust the touch slop if another gesture may be being performed.
                final float touchSlop =
                        event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
                        ? mTouchSlop * mSlopMultiplier
                        : mTouchSlop;
                if (h > touchSlop && h > Math.abs(x - mInitialTouchX)) {
                    mFalsingCollector.onNotificationStartDraggingDown();
                    mDraggingDown = true;
                    captureStartingChild(mInitialTouchX, mInitialTouchY);
                    mInitialTouchY = y;
                    mInitialTouchX = x;
                    mDragDownCallback.onTouchSlopExceeded();
                    return mStartingChild != null || mDragDownCallback.isDragDownAnywhereEnabled();
                }
                break;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!mDraggingDown) {
            return false;
        }
        final float x = event.getX();
        final float y = event.getY();

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_MOVE:
                mLastHeight = y - mInitialTouchY;
                captureStartingChild(mInitialTouchX, mInitialTouchY);
                if (mStartingChild != null) {
                    handleExpansion(mLastHeight, mStartingChild);
                } else {
                    mDragDownCallback.setEmptyDragAmount(mLastHeight);
                }
                if (mLastHeight > mMinDragDistance) {
                    if (!mDraggedFarEnough) {
                        mDraggedFarEnough = true;
                        mDragDownCallback.onCrossedThreshold(true);
                    }
                } else {
                    if (mDraggedFarEnough) {
                        mDraggedFarEnough = false;
                        mDragDownCallback.onCrossedThreshold(false);
                    }
                }
                return true;
            case MotionEvent.ACTION_UP:
                if (!mFalsingManager.isUnlockingDisabled() && mDragDownCallback.canDragDown()
                        && !isFalseTouch()) {
                    mDragDownCallback.onDraggedDown(mStartingChild, (int) (y - mInitialTouchY));
                    if (mStartingChild == null) {
                        cancelExpansion();
                    } else {
                        mCallback.setUserLockedChild(mStartingChild, false);
                        mStartingChild = null;
                    }
                    mDraggingDown = false;
                } else {
                    stopDragging();
                    return false;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                stopDragging();
                return false;
        }
        return false;
    }

    private boolean isFalseTouch() {
        if (!mDragDownCallback.isFalsingCheckNeeded()) {
            return false;
        }
        return mFalsingManager.isFalseTouch(NOTIFICATION_DRAG_DOWN) || !mDraggedFarEnough;
    }

    private void captureStartingChild(float x, float y) {
        if (mStartingChild == null) {
            mStartingChild = findView(x, y);
            if (mStartingChild != null) {
                if (mDragDownCallback.isDragDownEnabledForView(mStartingChild)) {
                    mCallback.setUserLockedChild(mStartingChild, true);
                } else {
                    mStartingChild = null;
                }
            }
        }
    }

    private void handleExpansion(float heightDelta, ExpandableView child) {
        if (heightDelta < 0) {
            heightDelta = 0;
        }
        boolean expandable = child.isContentExpandable();
        float rubberbandFactor = expandable
                ? RUBBERBAND_FACTOR_EXPANDABLE
                : RUBBERBAND_FACTOR_STATIC;
        float rubberband = heightDelta * rubberbandFactor;
        if (expandable
                && (rubberband + child.getCollapsedHeight()) > child.getMaxContentHeight()) {
            float overshoot =
                    (rubberband + child.getCollapsedHeight()) - child.getMaxContentHeight();
            overshoot *= (1 - RUBBERBAND_FACTOR_STATIC);
            rubberband -= overshoot;
        }
        child.setActualHeight((int) (child.getCollapsedHeight() + rubberband));
    }

    private void cancelExpansion(final ExpandableView child) {
        if (child.getActualHeight() == child.getCollapsedHeight()) {
            mCallback.setUserLockedChild(child, false);
            return;
        }
        ObjectAnimator anim = ObjectAnimator.ofInt(child, "actualHeight",
                child.getActualHeight(), child.getCollapsedHeight());
        anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
        anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS);
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mCallback.setUserLockedChild(child, false);
            }
        });
        anim.start();
    }

    private void cancelExpansion() {
        ValueAnimator anim = ValueAnimator.ofFloat(mLastHeight, 0);
        anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
        anim.setDuration(SPRING_BACK_ANIMATION_LENGTH_MS);
        anim.addUpdateListener(animation -> {
            mDragDownCallback.setEmptyDragAmount((Float) animation.getAnimatedValue());
        });
        anim.start();
    }

    private void stopDragging() {
        mFalsingCollector.onNotificationStopDraggingDown();
        if (mStartingChild != null) {
            cancelExpansion(mStartingChild);
            mStartingChild = null;
        } else {
            cancelExpansion();
        }
        mDraggingDown = false;
        mDragDownCallback.onDragDownReset();
    }

    private ExpandableView findView(float x, float y) {
        mHost.getLocationOnScreen(mTemp2);
        x += mTemp2[0];
        y += mTemp2[1];
        return mCallback.getChildAtRawPosition(x, y);
    }

    public boolean isDraggingDown() {
        return mDraggingDown;
    }

    public boolean isDragDownEnabled() {
        return mDragDownCallback.isDragDownEnabledForView(null);
    }

    public interface DragDownCallback {

        /**
         * @return true if the interaction is accepted, false if it should be cancelled
         */
        boolean canDragDown();

        /** Call when a view has been dragged. */
        void onDraggedDown(View startingChild, int dragLengthY);
        void onDragDownReset();

        /**
         * The user has dragged either above or below the threshold
         * @param above whether he dragged above it
         */
        void onCrossedThreshold(boolean above);
        void onTouchSlopExceeded();
        void setEmptyDragAmount(float amount);
        boolean isFalsingCheckNeeded();

        /**
         * Is dragging down enabled on a given view
         * @param view The view to check or {@code null} to check if it's enabled at all
         */
        boolean isDragDownEnabledForView(ExpandableView view);

        /**
         * @return if drag down is enabled anywhere, not just on selected views.
         */
        boolean isDragDownAnywhereEnabled();
    }
}
+263 −0
Original line number Diff line number Diff line
package com.android.systemui.statusbar

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.content.Context
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import com.android.systemui.ExpandHelper
import com.android.systemui.Gefingerpoken
import com.android.systemui.animation.Interpolators
import com.android.systemui.R
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.notification.row.ExpandableView

/**
 * A utility class to enable the downward swipe on the lockscreen to go to the full shade and expand
 * the notification where the drag started.
 */
class DragDownHelper(private val falsingManager: FalsingManager,
                     private val expandCallback: ExpandHelper.Callback,
                     private val dragDownCallback: DragDownCallback,
                     private val falsingCollector: FalsingCollector,
                     private val host: View,
                     context: Context): Gefingerpoken {

    private val minDragDistance: Int
    private var initialTouchX = 0f
    private var initialTouchY = 0f
    private val touchSlop: Float
    private val slopMultiplier: Float
    private val temp2 = IntArray(2)
    private var draggedFarEnough = false
    private var startingChild: ExpandableView? = null
    private var lastHeight = 0f
    var isDraggingDown = false
        private set

    init {
        minDragDistance = context.resources.getDimensionPixelSize(
                R.dimen.keyguard_drag_down_min_distance)
        val configuration = ViewConfiguration.get(context)
        touchSlop = configuration.scaledTouchSlop.toFloat()
        slopMultiplier = configuration.scaledAmbiguousGestureMultiplier
    }

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        val x = event.x
        val y = event.y
        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                draggedFarEnough = false
                isDraggingDown = false
                startingChild = null
                initialTouchY = y
                initialTouchX = x
            }
            MotionEvent.ACTION_MOVE -> {
                val h = y - initialTouchY
                // Adjust the touch slop if another gesture may be being performed.
                val touchSlop = if (event.classification
                        == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE)
                    touchSlop * slopMultiplier
                else
                    touchSlop
                if (h > touchSlop && h > Math.abs(x - initialTouchX)) {
                    falsingCollector.onNotificationStartDraggingDown()
                    isDraggingDown = true
                    captureStartingChild(initialTouchX, initialTouchY)
                    initialTouchY = y
                    initialTouchX = x
                    dragDownCallback.onTouchSlopExceeded()
                    return startingChild != null || dragDownCallback.isDragDownAnywhereEnabled
                }
            }
        }
        return false
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (!isDraggingDown) {
            return false
        }
        val x = event.x
        val y = event.y
        when (event.actionMasked) {
            MotionEvent.ACTION_MOVE -> {
                lastHeight = y - initialTouchY
                captureStartingChild(initialTouchX, initialTouchY)
                if (startingChild != null) {
                    handleExpansion(lastHeight, startingChild!!)
                } else {
                    dragDownCallback.setEmptyDragAmount(lastHeight)
                }
                if (lastHeight > minDragDistance) {
                    if (!draggedFarEnough) {
                        draggedFarEnough = true
                        dragDownCallback.onCrossedThreshold(true)
                    }
                } else {
                    if (draggedFarEnough) {
                        draggedFarEnough = false
                        dragDownCallback.onCrossedThreshold(false)
                    }
                }
                return true
            }
            MotionEvent.ACTION_UP -> if (!falsingManager.isUnlockingDisabled && !isFalseTouch
                    && dragDownCallback.canDragDown()) {
                dragDownCallback.onDraggedDown(startingChild, (y - initialTouchY).toInt())
                if (startingChild == null) {
                    cancelExpansion()
                } else {
                    expandCallback.setUserLockedChild(startingChild, false)
                    startingChild = null
                }
                isDraggingDown = false
            } else {
                stopDragging()
                return false
            }
            MotionEvent.ACTION_CANCEL -> {
                stopDragging()
                return false
            }
        }
        return false
    }

    private val isFalseTouch: Boolean
        get() {
            return if (!dragDownCallback.isFalsingCheckNeeded) {
                false
            } else {
                falsingManager.isFalseTouch(Classifier.NOTIFICATION_DRAG_DOWN) || !draggedFarEnough
            }
        }

    private fun captureStartingChild(x: Float, y: Float) {
        if (startingChild == null) {
            startingChild = findView(x, y)
            if (startingChild != null) {
                if (dragDownCallback.isDragDownEnabledForView(startingChild)) {
                    expandCallback.setUserLockedChild(startingChild, true)
                } else {
                    startingChild = null
                }
            }
        }
    }

    private fun handleExpansion(heightDelta: Float, child: ExpandableView) {
        var hDelta = heightDelta
        if (hDelta < 0) {
            hDelta = 0f
        }
        val expandable = child.isContentExpandable
        val rubberbandFactor = if (expandable) {
            RUBBERBAND_FACTOR_EXPANDABLE
        } else {
            RUBBERBAND_FACTOR_STATIC
        }
        var rubberband = hDelta * rubberbandFactor
        if (expandable && rubberband + child.collapsedHeight > child.maxContentHeight) {
            var overshoot = rubberband + child.collapsedHeight - child.maxContentHeight
            overshoot *= 1 - RUBBERBAND_FACTOR_STATIC
            rubberband -= overshoot
        }
        child.actualHeight = (child.collapsedHeight + rubberband).toInt()
    }

    private fun cancelExpansion(child: ExpandableView) {
        if (child.actualHeight == child.collapsedHeight) {
            expandCallback.setUserLockedChild(child, false)
            return
        }
        val anim = ObjectAnimator.ofInt(child, "actualHeight",
                child.actualHeight, child.collapsedHeight)
        anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
        anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
        anim.addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator) {
                expandCallback.setUserLockedChild(child, false)
            }
        })
        anim.start()
    }

    private fun cancelExpansion() {
        val anim = ValueAnimator.ofFloat(lastHeight, 0f)
        anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
        anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
        anim.addUpdateListener { animation: ValueAnimator ->
            dragDownCallback.setEmptyDragAmount(animation.animatedValue as Float)
        }
        anim.start()
    }

    private fun stopDragging() {
        falsingCollector.onNotificationStopDraggingDown()
        if (startingChild != null) {
            cancelExpansion(startingChild!!)
            startingChild = null
        } else {
            cancelExpansion()
        }
        isDraggingDown = false
        dragDownCallback.onDragDownReset()
    }

    private fun findView(x: Float, y: Float): ExpandableView {
        host.getLocationOnScreen(temp2)
        return expandCallback.getChildAtRawPosition(x + temp2[0] , y + temp2[1])
    }

    val isDragDownEnabled: Boolean
        get() = dragDownCallback.isDragDownEnabledForView(null)

    interface DragDownCallback {

        /**
         * @return true if the interaction is accepted, false if it should be cancelled
         */
        fun canDragDown(): Boolean

        /**
         * Call when a view has been dragged.
         **/
        fun onDraggedDown(startingChild: View?, dragLengthY: Int)
        fun onDragDownReset()

        /**
         * The user has dragged either above or below the threshold
         * @param above whether they dragged above it
         */
        fun onCrossedThreshold(above: Boolean)
        fun onTouchSlopExceeded()
        fun setEmptyDragAmount(amount: Float)
        val isFalsingCheckNeeded: Boolean

        /**
         * Is dragging down enabled on a given view
         * @param view The view to check or `null` to check if it's enabled at all
         */
        fun isDragDownEnabledForView(view: ExpandableView?): Boolean

        /**
         * @return if drag down is enabled anywhere, not just on selected views.
         */
        val isDragDownAnywhereEnabled: Boolean
    }

    companion object {
        private const val RUBBERBAND_FACTOR_EXPANDABLE = 0.5f
        private const val RUBBERBAND_FACTOR_STATIC = 0.15f
        private const val SPRING_BACK_ANIMATION_LENGTH_MS = 375
    }
}
 No newline at end of file
+2 −3
Original line number Diff line number Diff line
@@ -409,9 +409,8 @@ public class NotificationShadeWindowViewController {
        ExpandHelper.Callback expandHelperCallback = mStackScrollLayout.getExpandHelperCallback();
        DragDownHelper.DragDownCallback dragDownCallback = mStackScrollLayout.getDragDownCallback();
        setDragDownHelper(
                new DragDownHelper(
                        mView.getContext(), mView, expandHelperCallback,
                        dragDownCallback, mFalsingManager, mFalsingCollector));
                new DragDownHelper(mFalsingManager, expandHelperCallback, dragDownCallback,
                        mFalsingCollector, mView, mView.getContext()));

        mDepthController.setRoot(mView);
        mNotificationPanelViewController.addExpansionListener(mDepthController);