Convert DragDownHelper to Kotlin

Bug: 184946919
Test: build, test drag down behavior on lockscreen
Change-Id: I56eebf57a169ce6d22ad071d029135078a3b712b
This commit is contained in:
Selim Cinek
2021-04-13 16:34:25 +02:00
parent 429e2a8b6a
commit 91579bde59
3 changed files with 265 additions and 295 deletions

View File

@@ -1,292 +0,0 @@
/*
* 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();
}
}

View File

@@ -0,0 +1,263 @@
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
}
}

View File

@@ -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);