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

Commit 76d6afe9 authored by Bryce Lee's avatar Bryce Lee
Browse files

Allow swiping down notification shade over dream.

This changelist enables swiping down the notification shade over the
dream. NotificationShadeTouchHandler tracks swipes that originate from
the status bar area of the dream and redirects these motion events to
the NotificationPanelViewController.

Test: atest NotificationShadeTouchHandlerTest
Bug: 267565290
Change-Id: I72d54cf33601dacc0a2a6adfd2b7639615061c09
Merged-In: I72d54cf33601dacc0a2a6adfd2b7639615061c09
parent f296aa6c
Loading
Loading
Loading
Loading
+92 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 com.android.systemui.dreams.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;

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

import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.CentralSurfaces;

import java.util.Optional;

import javax.inject.Inject;
import javax.inject.Named;

/**
 * {@link ShadeTouchHandler} is responsible for handling swipe down gestures over dream
 * to bring down the shade.
 */
public class ShadeTouchHandler implements DreamTouchHandler {
    private final Optional<CentralSurfaces> mSurfaces;
    private final int mInitiationHeight;

    @Inject
    ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces,
            @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) {
        mSurfaces = centralSurfaces;
        mInitiationHeight = initiationHeight;
    }

    @Override
    public void onSessionStart(TouchSession session) {
        if (mSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) {
            session.pop();
            return;
        }

        session.registerInputListener(ev -> {
            final NotificationPanelViewController viewController =
                    mSurfaces.map(CentralSurfaces::getNotificationPanelViewController).orElse(null);

            if (viewController != null) {
                viewController.handleExternalTouch((MotionEvent) ev);
            }

            if (ev instanceof MotionEvent) {
                if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
                    session.pop();
                }
            }
        });

        session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
                    float distanceY) {
                return true;
            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                    float velocityY) {
                return true;
            }
        });
    }

    @Override
    public void getTouchInitiationRegion(Rect bounds, Region region) {
        final Rect outBounds = new Rect(bounds);
        outBounds.inset(0, 0, 0, outBounds.height() - mInitiationHeight);
        region.op(outBounds, Region.Op.UNION);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import dagger.Module;
@Module(includes = {
            BouncerSwipeModule.class,
            HideComplicationModule.class,
            ShadeModule.class,
        }, subcomponents = {
            InputSessionComponent.class,
})
+62 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 android.content.res.Resources;

import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.dreams.touch.ShadeTouchHandler;

import javax.inject.Named;

import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;

/**
 * Dependencies for swipe down to notification over dream.
 */
@Module
public class ShadeModule {
    /**
     * The height, defined in pixels, of the gesture initiation region at the top of the screen for
     * swiping down notifications.
     */
    public static final String NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT =
            "notification_shade_gesture_initiation_height";

    /**
     * Provides {@link ShadeTouchHandler} to handle notification swipe down over dream.
     */
    @Provides
    @IntoSet
    public static DreamTouchHandler providesNotificationShadeTouchHandler(
            ShadeTouchHandler touchHandler) {
        return touchHandler;
    }

    /**
     * Provides the height of the gesture area for notification swipe down.
     */
    @Provides
    @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT)
    public static int providesNotificationShadeGestureRegionHeight(@Main Resources resources) {
        return resources.getDimensionPixelSize(R.dimen.dream_overlay_status_bar_height);
    }
}
+116 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.testing.AndroidTestingRunner;
import android.view.GestureDetector;
import android.view.MotionEvent;

import androidx.test.filters.SmallTest;

import com.android.systemui.SysuiTestCase;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.phone.CentralSurfaces;

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;

import java.util.Optional;

@SmallTest
@RunWith(AndroidTestingRunner.class)
public class ShadeTouchHandlerTest extends SysuiTestCase {
    @Mock
    CentralSurfaces mCentralSurfaces;

    @Mock
    NotificationPanelViewController mNotificationPanelViewController;

    @Mock
    DreamTouchHandler.TouchSession mTouchSession;

    ShadeTouchHandler mTouchHandler;

    private static final int TOUCH_HEIGHT = 20;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces),
                TOUCH_HEIGHT);
        when(mCentralSurfaces.getNotificationPanelViewController())
                .thenReturn(mNotificationPanelViewController);
    }

    /**
     * Verify that touches aren't handled when the bouncer is showing.
     */
    @Test
    public void testInactiveOnBouncer() {
        when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
        mTouchHandler.onSessionStart(mTouchSession);
        verify(mTouchSession).pop();
    }

    /**
     * Make sure {@link ShadeTouchHandler}
     */
    @Test
    public void testTouchPilferingOnScroll() {
        final MotionEvent motionEvent1 = Mockito.mock(MotionEvent.class);
        final MotionEvent motionEvent2 = Mockito.mock(MotionEvent.class);

        final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerArgumentCaptor =
                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);

        mTouchHandler.onSessionStart(mTouchSession);
        verify(mTouchSession).registerGestureListener(gestureListenerArgumentCaptor.capture());

        assertThat(gestureListenerArgumentCaptor.getValue()
                .onScroll(motionEvent1, motionEvent2, 1, 1))
                .isTrue();
    }

    /**
     * Ensure touches are propagated to the {@link NotificationPanelViewController}.
     */
    @Test
    public void testEventPropagation() {
        final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);

        final ArgumentCaptor<InputChannelCompat.InputEventListener>
                inputEventListenerArgumentCaptor =
                    ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);

        mTouchHandler.onSessionStart(mTouchSession);
        verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture());
        inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent);
        verify(mNotificationPanelViewController).handleExternalTouch(motionEvent);
    }

}