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

Commit ffe63c31 authored by Bryce Lee's avatar Bryce Lee Committed by Android (Google) Code Review
Browse files

Merge "Add Touch Initiation Regions." into tm-dev

parents 427007c4 92748079
Loading
Loading
Loading
Loading
+26 −34
Original line number Diff line number Diff line
@@ -21,6 +21,9 @@ import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_
import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;

import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.InputEvent;
@@ -75,6 +78,8 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
    private final FlingAnimationUtils mFlingAnimationUtils;
    private final FlingAnimationUtils mFlingAnimationUtilsClosing;

    private final DisplayMetrics mDisplayMetrics;

    private Boolean mCapture;

    private TouchSession mTouchSession;
@@ -85,40 +90,9 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {

    private final GestureDetector.OnGestureListener mOnGestureListener =
            new  GestureDetector.SimpleOnGestureListener() {
                boolean mTrack;
                boolean mBouncerPresent;

                @Override
                public boolean onDown(MotionEvent e) {
                    // We only consider gestures that originate from the lower portion of the
                    // screen.
                    final float displayHeight = mStatusBar.getDisplayHeight();

                    mBouncerPresent = mStatusBar.isBouncerShowing();

                    // The target zone is either at the top or bottom of the screen, dependent on
                    // whether the bouncer is present.
                    final float zonePercentage =
                            Math.abs(e.getY() - (mBouncerPresent ? 0 : displayHeight))
                                    / displayHeight;

                    mTrack =  zonePercentage < mBouncerZoneScreenPercentage;

                    // Never capture onDown. While this might lead to some false positive touches
                    // being sent to other windows/layers, this is necessary to make sure the
                    // proper touch event sequence is received by others in the event we do not
                    // consume the sequence here.
                    return false;
                }

                @Override
                public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
                        float distanceY) {
                    // Do not handle scroll gestures if not tracking touch events.
                    if (!mTrack) {
                        return false;
                    }

                    if (mCapture == null) {
                        // If the user scrolling favors a vertical direction, begin capturing
                        // scrolls.
@@ -141,8 +115,8 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
                    // (0).
                    final float screenTravelPercentage =
                            Math.abs((e1.getY() - e2.getY()) / mStatusBar.getDisplayHeight());
                    setPanelExpansion(
                            mBouncerPresent ? screenTravelPercentage : 1 - screenTravelPercentage);
                    setPanelExpansion(mStatusBar.isBouncerShowing()
                            ? screenTravelPercentage : 1 - screenTravelPercentage);

                    return true;
                }
@@ -155,6 +129,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {

    @Inject
    public BouncerSwipeTouchHandler(
            DisplayMetrics displayMetrics,
            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
            StatusBar statusBar,
            NotificationShadeWindowController notificationShadeWindowController,
@@ -165,6 +140,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
            @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
                    FlingAnimationUtils flingAnimationUtilsClosing,
            @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage) {
        mDisplayMetrics = displayMetrics;
        mStatusBar = statusBar;
        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
        mNotificationShadeWindowController = notificationShadeWindowController;
@@ -175,6 +151,21 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
        mVelocityTrackerFactory = velocityTrackerFactory;
    }

    @Override
    public void getTouchInitiationRegion(Region region) {
        if (mStatusBar.isBouncerShowing()) {
            region.op(new Rect(0, 0, mDisplayMetrics.widthPixels,
                    Math.round(mDisplayMetrics.heightPixels * mBouncerZoneScreenPercentage)),
                    Region.Op.UNION);
        } else {
            region.op(new Rect(0,
                    Math.round(mDisplayMetrics.heightPixels * (1 - mBouncerZoneScreenPercentage)),
                    mDisplayMetrics.widthPixels,
                    mDisplayMetrics.heightPixels),
                    Region.Op.UNION);
        }
    }

    @Override
    public void onSessionStart(TouchSession session) {
        mVelocityTracker = mVelocityTrackerFactory.obtain();
@@ -202,7 +193,9 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
        final MotionEvent motionEvent = (MotionEvent) event;

        switch(motionEvent.getAction()) {
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                mTouchSession.pop();
                // If we are not capturing any input, there is no need to consider animating to
                // finish transition.
                if (mCapture == null || !mCapture) {
@@ -226,7 +219,6 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
                if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) {
                    mStatusBarKeyguardViewManager.reset(false);
                }
                mTouchSession.pop();
                break;
            default:
                mVelocityTracker.addMovement(motionEvent);
+39 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.dreams.touch;

import android.graphics.Region;
import android.view.GestureDetector;
import android.view.InputEvent;
import android.view.MotionEvent;
@@ -34,6 +35,7 @@ import com.android.systemui.shared.system.InputChannelCompat;
import com.google.common.util.concurrent.ListenableFuture;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -100,6 +102,10 @@ public class DreamOverlayTouchMonitor {
        });
    }

    private int getSessionCount() {
        return mActiveTouchSessions.size();
    }

    /**
     * {@link TouchSessionImpl} implements {@link DreamTouchHandler.TouchSession} for
     * {@link DreamOverlayTouchMonitor}. It enables the monitor to access the associated listeners
@@ -146,6 +152,11 @@ public class DreamOverlayTouchMonitor {
            return mTouchMonitor.pop(this);
        }

        @Override
        public int getActiveSessionCount() {
            return mTouchMonitor.getSessionCount();
        }

        /**
         * Returns the active listeners to receive touch events.
         */
@@ -229,12 +240,39 @@ public class DreamOverlayTouchMonitor {
        public void onInputEvent(InputEvent ev) {
            // No Active sessions are receiving touches. Create sessions for each listener
            if (mActiveTouchSessions.isEmpty()) {
                final HashMap<DreamTouchHandler, DreamTouchHandler.TouchSession> sessionMap =
                        new HashMap<>();

                for (DreamTouchHandler handler : mHandlers) {
                    final Region initiationRegion = Region.obtain();
                    handler.getTouchInitiationRegion(initiationRegion);

                    if (!initiationRegion.isEmpty()) {
                        // Initiation regions require a motion event to determine pointer location
                        // within the region.
                        if (!(ev instanceof MotionEvent)) {
                            continue;
                        }

                        final MotionEvent motionEvent = (MotionEvent) ev;

                        // If the touch event is outside the region, then ignore.
                        if (!initiationRegion.contains(Math.round(motionEvent.getX()),
                                Math.round(motionEvent.getY()))) {
                            continue;
                        }
                    }

                    final TouchSessionImpl sessionStack =
                            new TouchSessionImpl(DreamOverlayTouchMonitor.this, null);
                    mActiveTouchSessions.add(sessionStack);
                    handler.onSessionStart(sessionStack);
                    sessionMap.put(handler, sessionStack);
                }

                // Informing handlers of new sessions is delayed until we have all created so the
                // final session is correct.
                sessionMap.forEach((dreamTouchHandler, touchSession)
                        -> dreamTouchHandler.onSessionStart(touchSession));
            }

            // Find active sessions and invoke on InputEvent.
+14 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.dreams.touch;

import android.graphics.Region;
import android.view.GestureDetector;

import com.android.systemui.shared.system.InputChannelCompat;
@@ -71,6 +72,19 @@ public interface DreamTouchHandler {
         * if the popped {@link TouchSession} was the initial session or has already been popped.
         */
        ListenableFuture<TouchSession> pop();

        /**
         * Returns the number of currently active sessions.
         */
        int getActiveSessionCount();
    }

    /**
     * Returns the region the touch handler is interested in. By default, no region is specified,
     * indicating the entire screen should be considered.
     * @param region A {@link Region} that is passed in to the target entry touch region.
     */
    default void getTouchInitiationRegion(Region region) {
    }

    /**
+27 −41
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.systemui.dreams.touch;

import static com.google.common.truth.Truth.assertThat;


import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -27,12 +26,14 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.graphics.Region;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.VelocityTracker;

import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;

import com.android.systemui.SysuiTestCase;
@@ -51,8 +52,6 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.util.Random;

@SmallTest
@RunWith(AndroidTestingRunner.class)
public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
@@ -89,13 +88,20 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
    @Mock
    VelocityTracker mVelocityTracker;

    final DisplayMetrics mDisplayMetrics = new DisplayMetrics();

    private static final float TOUCH_REGION = .3f;
    private static final float SCREEN_HEIGHT_PX = 100;
    private static final int SCREEN_WIDTH_PX = 1024;
    private static final int SCREEN_HEIGHT_PX = 100;

    @Before
    public void setup() {
        mDisplayMetrics.widthPixels = SCREEN_WIDTH_PX;
        mDisplayMetrics.heightPixels = SCREEN_HEIGHT_PX;

        MockitoAnnotations.initMocks(this);
        mTouchHandler = new BouncerSwipeTouchHandler(
                mDisplayMetrics,
                mStatusBarKeyguardViewManager,
                mStatusBar,
                mNotificationShadeWindowController,
@@ -104,23 +110,29 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
                mFlingAnimationUtils,
                mFlingAnimationUtilsClosing,
                TOUCH_REGION);
        when(mStatusBar.getDisplayHeight()).thenReturn(SCREEN_HEIGHT_PX);

        when(mStatusBar.getDisplayHeight()).thenReturn((float) SCREEN_HEIGHT_PX);
        when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
        when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
    }

    private static void beginValidSwipe(GestureDetector.OnGestureListener listener) {
        listener.onDown(MotionEvent.obtain(0, 0,
                MotionEvent.ACTION_DOWN, 0,
                SCREEN_HEIGHT_PX - (.5f * TOUCH_REGION * SCREEN_HEIGHT_PX), 0));
    }

    /**
     * Ensures expansion only happens when touch down happens in valid part of the screen.
     */
    @FlakyTest
    @Test
    public void testSessionStart() {
        final Region region = Region.obtain();
        mTouchHandler.getTouchInitiationRegion(region);

        final Rect bounds = region.getBounds();

        final Rect expected = new Rect();

        expected.set(0, Math.round(SCREEN_HEIGHT_PX * (1 - TOUCH_REGION)), SCREEN_WIDTH_PX,
                SCREEN_HEIGHT_PX);

        assertThat(bounds).isEqualTo(expected);

        mTouchHandler.onSessionStart(mTouchSession);
        verify(mNotificationShadeWindowController).setForcePluginOpen(eq(true), any());
        ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor =
@@ -130,34 +142,12 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
        verify(mTouchSession).registerInputListener(eventListenerCaptor.capture());

        final Random random = new Random(System.currentTimeMillis());

        // If an initial touch down meeting criteria has been met, scroll behavior should be
        // ignored.
        assertThat(gestureListenerCaptor.getValue()
                .onScroll(Mockito.mock(MotionEvent.class),
                        Mockito.mock(MotionEvent.class),
                        random.nextFloat(),
                        random.nextFloat())).isFalse();

        // A touch at the top of the screen should also not trigger listening.
        final MotionEvent touchDownEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN,
                0, 0, 0);

        gestureListenerCaptor.getValue().onDown(touchDownEvent);
        assertThat(gestureListenerCaptor.getValue()
                .onScroll(Mockito.mock(MotionEvent.class),
                        Mockito.mock(MotionEvent.class),
                        random.nextFloat(),
                        random.nextFloat())).isFalse();

        // A touch within range at the bottom of the screen should trigger listening
        beginValidSwipe(gestureListenerCaptor.getValue());
        assertThat(gestureListenerCaptor.getValue()
                .onScroll(Mockito.mock(MotionEvent.class),
                        Mockito.mock(MotionEvent.class),
                        random.nextFloat(),
                        random.nextFloat())).isTrue();
                        1,
                        2)).isTrue();
    }

    /**
@@ -170,8 +160,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());

        beginValidSwipe(gestureListenerCaptor.getValue());

        final float scrollAmount = .3f;
        final float distanceY = SCREEN_HEIGHT_PX * scrollAmount;

@@ -203,8 +191,6 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {

        when(mVelocityTracker.getYVelocity()).thenReturn(velocityY);

        beginValidSwipe(gestureListenerCaptor.getValue());

        final float distanceY = SCREEN_HEIGHT_PX * position;

        final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+78 −0
Original line number Diff line number Diff line
@@ -21,10 +21,13 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.graphics.Rect;
import android.graphics.Region;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.GestureDetector;
@@ -136,6 +139,81 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
        }
    }

    @Test
    public void testEntryTouchZone() {
        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
        final Rect touchArea = new Rect(4, 4, 8 , 8);

        doAnswer(invocation -> {
            final Region region = (Region) invocation.getArguments()[0];
            region.set(touchArea);
            return null;
        }).when(touchHandler).getTouchInitiationRegion(any());

        final Environment environment = new Environment(Stream.of(touchHandler)
                .collect(Collectors.toCollection(HashSet::new)));

        // Ensure touch outside specified region is not delivered.
        final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
        when(initialEvent.getX()).thenReturn(0.0f);
        when(initialEvent.getY()).thenReturn(1.0f);
        environment.publishInputEvent(initialEvent);
        verify(touchHandler, never()).onSessionStart(any());

        // Make sure touch inside region causes session start.
        when(initialEvent.getX()).thenReturn(5.0f);
        when(initialEvent.getY()).thenReturn(5.0f);
        environment.publishInputEvent(initialEvent);
        verify(touchHandler).onSessionStart(any());
    }

    @Test
    public void testSessionCount() {
        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
        final Rect touchArea = new Rect(4, 4, 8 , 8);

        final DreamTouchHandler unzonedTouchHandler = Mockito.mock(DreamTouchHandler.class);
        doAnswer(invocation -> {
            final Region region = (Region) invocation.getArguments()[0];
            region.set(touchArea);
            return null;
        }).when(touchHandler).getTouchInitiationRegion(any());

        final Environment environment = new Environment(Stream.of(touchHandler, unzonedTouchHandler)
                .collect(Collectors.toCollection(HashSet::new)));

        // Ensure touch outside specified region is delivered to unzoned touch handler.
        final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
        when(initialEvent.getX()).thenReturn(0.0f);
        when(initialEvent.getY()).thenReturn(1.0f);
        environment.publishInputEvent(initialEvent);

        ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionCaptor = ArgumentCaptor.forClass(
                DreamTouchHandler.TouchSession.class);

        // Make sure only one active session.
        {
            verify(unzonedTouchHandler).onSessionStart(touchSessionCaptor.capture());
            final DreamTouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
            assertThat(touchSession.getActiveSessionCount()).isEqualTo(1);
            touchSession.pop();
            environment.executeAll();
        }

        // Make sure touch inside the touch region.
        when(initialEvent.getX()).thenReturn(5.0f);
        when(initialEvent.getY()).thenReturn(5.0f);
        environment.publishInputEvent(initialEvent);

        // Make sure there are two active sessions.
        {
            verify(touchHandler).onSessionStart(touchSessionCaptor.capture());
            final DreamTouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
            assertThat(touchSession.getActiveSessionCount()).isEqualTo(2);
            touchSession.pop();
        }
    }

    @Test
    public void testInputEventPropagation() {
        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);