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

Commit 28a0de9c authored by Adrian Roos's avatar Adrian Roos
Browse files

Shade: Factor out DoubleTapHelper

Factors DoubleTapHelper out of ActivatableNotificationView, such that the
same logic can be reused for the StatusBarWindowView.

Bug: 30876804
Test: have a notification, lock phone, interact with notifications on the lockscreen
Change-Id: I4f3a43859d81677d5168424874b106bbfdea0be5
parent c98c16de
Loading
Loading
Loading
Loading
+11 −81
Original line number Original line Diff line number Diff line
@@ -38,6 +38,7 @@ import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.phone.DoubleTapHelper;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import com.android.systemui.statusbar.stack.StackStateAnimator;


@@ -47,7 +48,6 @@ import com.android.systemui.statusbar.stack.StackStateAnimator;
 */
 */
public abstract class ActivatableNotificationView extends ExpandableOutlineView {
public abstract class ActivatableNotificationView extends ExpandableOutlineView {


    private static final long DOUBLETAP_TIMEOUT_MS = 1200;
    private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220;
    private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220;
    private static final int ACTIVATE_ANIMATION_LENGTH = 220;
    private static final int ACTIVATE_ANIMATION_LENGTH = 220;
    private static final int DARK_ANIMATION_LENGTH = 170;
    private static final int DARK_ANIMATION_LENGTH = 170;
@@ -101,6 +101,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
    private final int mLowPriorityRippleColor;
    private final int mLowPriorityRippleColor;
    protected final int mNormalRippleColor;
    protected final int mNormalRippleColor;
    private final AccessibilityManager mAccessibilityManager;
    private final AccessibilityManager mAccessibilityManager;
    private final DoubleTapHelper mDoubleTapHelper;


    private boolean mDimmed;
    private boolean mDimmed;
    private boolean mDark;
    private boolean mDark;
@@ -114,14 +115,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
     */
     */
    private boolean mActivated;
    private boolean mActivated;


    private float mDownX;
    private float mDownY;
    private final float mTouchSlop;

    private float mActivationX;
    private float mActivationY;
    private final float mDoubleTapSlop;

    private OnActivatedListener mOnActivatedListener;
    private OnActivatedListener mOnActivatedListener;


    private final Interpolator mSlowOutFastInInterpolator;
    private final Interpolator mSlowOutFastInInterpolator;
@@ -143,7 +136,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
    private final int mLowPriorityColor;
    private final int mLowPriorityColor;
    private boolean mIsBelowSpeedBump;
    private boolean mIsBelowSpeedBump;
    private FalsingManager mFalsingManager;
    private FalsingManager mFalsingManager;
    private boolean mTrackTouch;


    private float mNormalBackgroundVisibilityAmount;
    private float mNormalBackgroundVisibilityAmount;
    private ValueAnimator mFadeInFromDarkAnimator;
    private ValueAnimator mFadeInFromDarkAnimator;
@@ -183,8 +175,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView


    public ActivatableNotificationView(Context context, AttributeSet attrs) {
    public ActivatableNotificationView(Context context, AttributeSet attrs) {
        super(context, attrs);
        super(context, attrs);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mDoubleTapSlop = context.getResources().getDimension(R.dimen.double_tap_slop);
        mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
        mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
        mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
        mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
        setClipChildren(false);
        setClipChildren(false);
@@ -200,6 +190,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
                R.color.notification_ripple_untinted_color);
                R.color.notification_ripple_untinted_color);
        mFalsingManager = FalsingManager.getInstance(context);
        mFalsingManager = FalsingManager.getInstance(context);
        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
        mAccessibilityManager = AccessibilityManager.getInstance(mContext);

        mDoubleTapHelper = new DoubleTapHelper(this, (active) -> {
            if (active) {
                makeActive();
            } else {
                makeInactive(true /* animate */);
            }
        }, this::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
    }
    }


    @Override
    @Override
@@ -284,60 +282,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
    }
    }


    private boolean handleTouchEventDimmed(MotionEvent event) {
    private boolean handleTouchEventDimmed(MotionEvent event) {
        int action = event.getActionMasked();
        return mDoubleTapHelper.onTouchEvent(event, getActualHeight());
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mDownX = event.getX();
                mDownY = event.getY();
                mTrackTouch = true;
                if (mDownY > getActualHeight()) {
                    mTrackTouch = false;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isWithinTouchSlop(event)) {
                    makeInactive(true /* animate */);
                    mTrackTouch = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                if (isWithinTouchSlop(event)) {
                    if (handleSlideBack()) {
                        return true;
                    }
                    if (!mActivated) {
                        makeActive();
                        postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
                        mActivationX = event.getX();
                        mActivationY = event.getY();
                    } else {
                        boolean withinDoubleTapSlop = isWithinDoubleTapSlop(event);
                        mFalsingManager.onNotificationDoubleTap(
                                withinDoubleTapSlop,
                                event.getX() - mActivationX,
                                event.getY() - mActivationY);
                        if (withinDoubleTapSlop) {
                            if (!performClick()) {
                                return false;
                            }
                        } else {
                            makeInactive(true /* animate */);
                            mTrackTouch = false;
                        }
                    }
                } else {
                    makeInactive(true /* animate */);
                    mTrackTouch = false;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                makeInactive(true /* animate */);
                mTrackTouch = false;
                break;
            default:
                break;
        }
        return mTrackTouch;
    }
    }


    private void makeActive() {
    private void makeActive() {
@@ -425,21 +370,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
        removeCallbacks(mTapTimeoutRunnable);
        removeCallbacks(mTapTimeoutRunnable);
    }
    }


    private boolean isWithinTouchSlop(MotionEvent event) {
        return Math.abs(event.getX() - mDownX) < mTouchSlop
                && Math.abs(event.getY() - mDownY) < mTouchSlop;
    }

    private boolean isWithinDoubleTapSlop(MotionEvent event) {
        if (!mActivated) {
            // If we're not activated there's no double tap slop to satisfy.
            return true;
        }

        return Math.abs(event.getX() - mActivationX) < mDoubleTapSlop
                && Math.abs(event.getY() - mActivationY) < mDoubleTapSlop;
    }

    public void setDimmed(boolean dimmed, boolean fade) {
    public void setDimmed(boolean dimmed, boolean fade) {
        if (mDimmed != dimmed) {
        if (mDimmed != dimmed) {
            mDimmed = dimmed;
            mDimmed = dimmed;
+174 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2017 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.phone;

import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import com.android.systemui.R;

/**
 * Detects a double tap.
 */
public class DoubleTapHelper {

    private static final long DOUBLETAP_TIMEOUT_MS = 1200;

    private final View mView;
    private final ActivationListener mActivationListener;
    private final DoubleTapListener mDoubleTapListener;
    private final SlideBackListener mSlideBackListener;
    private final DoubleTapLogListener mDoubleTapLogListener;

    private float mTouchSlop;
    private float mDoubleTapSlop;

    private boolean mActivated;

    private float mDownX;
    private float mDownY;
    private boolean mTrackTouch;

    private float mActivationX;
    private float mActivationY;
    private Runnable mTapTimeoutRunnable = this::makeInactive;

    public DoubleTapHelper(View view, ActivationListener activationListener,
            DoubleTapListener doubleTapListener, SlideBackListener slideBackListener,
            DoubleTapLogListener doubleTapLogListener) {
        mTouchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
        mDoubleTapSlop = view.getResources().getDimension(R.dimen.double_tap_slop);
        mView = view;

        mActivationListener = activationListener;
        mDoubleTapListener = doubleTapListener;
        mSlideBackListener = slideBackListener;
        mDoubleTapLogListener = doubleTapLogListener;
    }

    public boolean onTouchEvent(MotionEvent event) {
        return onTouchEvent(event, Integer.MAX_VALUE);
    }

    public boolean onTouchEvent(MotionEvent event, int maxTouchableHeight) {
        int action = event.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mDownX = event.getX();
                mDownY = event.getY();
                mTrackTouch = true;
                if (mDownY > maxTouchableHeight) {
                    mTrackTouch = false;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isWithinTouchSlop(event)) {
                    makeInactive();
                    mTrackTouch = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                if (isWithinTouchSlop(event)) {
                    if (mSlideBackListener != null && mSlideBackListener.onSlideBack()) {
                        return true;
                    }
                    if (!mActivated) {
                        makeActive();
                        mView.postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
                        mActivationX = event.getX();
                        mActivationY = event.getY();
                    } else {
                        boolean withinDoubleTapSlop = isWithinDoubleTapSlop(event);
                        if (mDoubleTapLogListener != null) {
                            mDoubleTapLogListener.onDoubleTapLog(withinDoubleTapSlop,
                                    event.getX() - mActivationX,
                                    event.getY() - mActivationY);
                        }
                        if (withinDoubleTapSlop) {
                            if (!mDoubleTapListener.onDoubleTap()) {
                                return false;
                            }
                        } else {
                            makeInactive();
                            mTrackTouch = false;
                        }
                    }
                } else {
                    makeInactive();
                    mTrackTouch = false;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                makeInactive();
                mTrackTouch = false;
                break;
            default:
                break;
        }
        return mTrackTouch;
    }

    private void makeActive() {
        if (!mActivated) {
            mActivated = true;
            mActivationListener.onActiveChanged(true);
        }
    }

    private void makeInactive() {
        if (mActivated) {
            mActivated = false;
            mActivationListener.onActiveChanged(false);
        }
    }

    private boolean isWithinTouchSlop(MotionEvent event) {
        return Math.abs(event.getX() - mDownX) < mTouchSlop
                && Math.abs(event.getY() - mDownY) < mTouchSlop;
    }

    private boolean isWithinDoubleTapSlop(MotionEvent event) {
        if (!mActivated) {
            // If we're not activated there's no double tap slop to satisfy.
            return true;
        }

        return Math.abs(event.getX() - mActivationX) < mDoubleTapSlop
                && Math.abs(event.getY() - mActivationY) < mDoubleTapSlop;
    }

    @FunctionalInterface
    public interface ActivationListener {
        void onActiveChanged(boolean active);
    }

    @FunctionalInterface
    public interface DoubleTapListener {
        boolean onDoubleTap();
    }

    @FunctionalInterface
    public interface SlideBackListener {
        boolean onSlideBack();
    }

    @FunctionalInterface
    public interface DoubleTapLogListener {
        void onDoubleTapLog(boolean accepted, float dx, float dy);
    }
}