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

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

Merge changes Ied60cbab,I2769b0ad into main

* changes:
  Only pilfer once in InputSession.
  Refactor InputSession dependencies.
parents e6917418 ce3c2103
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -774,3 +774,13 @@ flag {
    purpose: PURPOSE_BUGFIX
  }
}

flag {
    name: "dream_input_session_pilfer_once"
    namespace: "systemui"
    description: "Pilfer at most once per input session"
    bug: "324600132"
    metadata {
      purpose: PURPOSE_BUGFIX
    }
}
+23 −13
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.systemui.dreams.touch;

import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.INPUT_SESSION_NAME;
import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.PILFER_ON_GESTURE_CONSUME;

import android.os.Looper;
@@ -24,7 +23,8 @@ import android.view.Choreographer;
import android.view.GestureDetector;
import android.view.MotionEvent;

import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.Flags;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;

@@ -42,26 +42,34 @@ public class InputSession {
    private final InputChannelCompat.InputEventReceiver mInputEventReceiver;
    private final GestureDetector mGestureDetector;

    // Pilfering is a destructive operation. Once pilfering starts, the all events will be captured
    // by the associated monitor. We track whether we're pilfering since initiating pilfering
    // requires reaching out to the InputManagerService, which can be a heavy operation. This is
    // especially costly if this is happening on a continuous stream of motion events.
    private boolean mPilfering;

    /**
     * Default session constructor.
     * @param sessionName The session name that will be applied to the underlying
     * {@link InputMonitorCompat}.
     * @param inputMonitor Input monitor to track input events.
     * @param gestureDetector Gesture detector for detecting gestures.
     * @param inputEventListener A listener to receive input events.
     * @param gestureListener A listener to receive gesture events.
     * @param choreographer Choreographer to use with the input receiver.
     * @param looper Looper to use with the input receiver
     * @param pilferOnGestureConsume Whether touch events should be pilfered after a gesture has
     *                               been consumed.
     */
    @Inject
    public InputSession(@Named(INPUT_SESSION_NAME) String sessionName,
    public InputSession(
            InputMonitorCompat inputMonitor,
            GestureDetector gestureDetector,
            InputChannelCompat.InputEventListener inputEventListener,
            GestureDetector.OnGestureListener gestureListener,
            DisplayTracker displayTracker,
            Choreographer choreographer,
            @Main Looper looper,
            @Named(PILFER_ON_GESTURE_CONSUME) boolean pilferOnGestureConsume) {
        mInputMonitor = new InputMonitorCompat(sessionName, displayTracker.getDefaultDisplayId());
        mGestureDetector = new GestureDetector(gestureListener);
        mInputMonitor = inputMonitor;
        mGestureDetector = gestureDetector;

        mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(),
                Choreographer.getInstance(),
        mInputEventReceiver = mInputMonitor.getInputReceiver(looper, choreographer,
                ev -> {
                    // Process event. Since sometimes input may be a prerequisite for some
                    // gesture logic, process input first.
@@ -69,7 +77,9 @@ public class InputSession {

                    if (ev instanceof MotionEvent
                            && mGestureDetector.onTouchEvent((MotionEvent) ev)
                            && pilferOnGestureConsume) {
                            && pilferOnGestureConsume
                            && !(mPilfering && Flags.dreamInputSessionPilferOnce())) {
                        mPilfering = true;
                        mInputMonitor.pilferPointers();
                    }
                });
+5 −3
Original line number Diff line number Diff line
@@ -24,16 +24,18 @@ import android.view.GestureDetector;
import com.android.systemui.dreams.touch.InputSession;
import com.android.systemui.shared.system.InputChannelCompat;

import javax.inject.Named;

import dagger.BindsInstance;
import dagger.Subcomponent;

import javax.inject.Named;

/**
 * {@link InputSessionComponent} generates {@link InputSession} with specific instances bound for
 * the session name and whether touches should be pilfered when consumed.
 */
@Subcomponent
@Subcomponent(
        modules = { InputSessionModule.class }
)
public interface InputSessionComponent {
    /**
     * Generates {@link InputSessionComponent}.
+50 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.dreams.touch.dagger;

import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.INPUT_SESSION_NAME;

import android.view.GestureDetector;

import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shared.system.InputMonitorCompat;

import dagger.Module;
import dagger.Provides;

import javax.inject.Named;


/**
 * Module for providing dependencies to {@link com.android.systemui.dreams.touch.InputSession}.
 */
@Module
public interface InputSessionModule {
    /** */
    @Provides
    static InputMonitorCompat providesInputMonitorCompat(@Named(INPUT_SESSION_NAME) String name,
            DisplayTracker displayTracker) {
        return new InputMonitorCompat(name, displayTracker.getDefaultDisplayId());
    }

    /** */
    @Provides
    static GestureDetector providesGestureDetector(
            android.view.GestureDetector.OnGestureListener gestureListener) {
        return new GestureDetector(gestureListener);
    }
}
+155 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.dreams.touch;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Choreographer;
import android.view.GestureDetector;
import android.view.InputEvent;
import android.view.MotionEvent;

import androidx.test.filters.SmallTest;

import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

/**
 * A test suite for exercising {@link InputSession}.
 */
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper()
public class InputSessionTest extends SysuiTestCase {
    @Mock
    InputMonitorCompat mInputMonitor;

    @Mock
    GestureDetector mGestureDetector;

    @Mock
    InputChannelCompat.InputEventListener mInputEventListener;

    TestableLooper mLooper;

    @Mock
    Choreographer mChoreographer;

    @Mock
    InputChannelCompat.InputEventReceiver mInputEventReceiver;

    InputSession mSession;

    InputChannelCompat.InputEventListener mEventListener;


    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mLooper = TestableLooper.get(this);
    }

    private void createSession(boolean pilfer) {
        when(mInputMonitor.getInputReceiver(any(), any(), any()))
                .thenReturn(mInputEventReceiver);
        mSession = new InputSession(mInputMonitor, mGestureDetector,
                mInputEventListener, mChoreographer, mLooper.getLooper(), pilfer);
        final ArgumentCaptor<InputChannelCompat.InputEventListener> listenerCaptor =
                ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
        verify(mInputMonitor).getInputReceiver(any(), any(), listenerCaptor.capture());
        mEventListener = listenerCaptor.getValue();
    }

    /**
     * Ensures consumed motion events are pilfered when option is set.
     */
    @Test
    public void testPilferOnMotionEventGestureConsume() {
        createSession(true);
        final MotionEvent event = Mockito.mock(MotionEvent.class);
        when(mGestureDetector.onTouchEvent(event)).thenReturn(true);
        mEventListener.onInputEvent(event);
        verify(mInputEventListener).onInputEvent(eq(event));
        verify(mInputMonitor).pilferPointers();
    }

    /**
     * Ensures consumed motion events are not pilfered when option is not set.
     */
    @Test
    public void testNoPilferOnMotionEventGestureConsume() {
        createSession(false);
        final MotionEvent event = Mockito.mock(MotionEvent.class);
        when(mGestureDetector.onTouchEvent(event)).thenReturn(true);
        mEventListener.onInputEvent(event);
        verify(mInputEventListener).onInputEvent(eq(event));
        verify(mInputMonitor, never()).pilferPointers();
    }

    /**
     * Ensures input events are never pilfered.
     */
    @Test
    public void testNoPilferOnInputEvent() {
        createSession(true);
        final InputEvent event = Mockito.mock(InputEvent.class);
        mEventListener.onInputEvent(event);
        verify(mInputEventListener).onInputEvent(eq(event));
        verify(mInputMonitor, never()).pilferPointers();
    }

    @Test
    @EnableFlags(Flags.FLAG_DREAM_INPUT_SESSION_PILFER_ONCE)
    public void testPilferOnce() {
        createSession(true);
        final MotionEvent event = Mockito.mock(MotionEvent.class);
        when(mGestureDetector.onTouchEvent(event)).thenReturn(true);
        mEventListener.onInputEvent(event);
        mEventListener.onInputEvent(event);
        verify(mInputEventListener, times(2)).onInputEvent(eq(event));
        verify(mInputMonitor, times(1)).pilferPointers();
    }

    /**
     * Ensures components are properly disposed.
     */
    @Test
    public void testDispose() {
        createSession(true);
        mSession.dispose();
        verify(mInputMonitor).dispose();
        verify(mInputEventReceiver).dispose();
    }
}