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

Commit ef889a1c authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Automerger Merge Worker
Browse files

Merge "Multi-shade foundation - integration (5/5)." into udc-dev am: 0003196b

parents 8c0b8017 0003196b
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ 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.
  ~
  -->

<com.android.systemui.multishade.ui.view.MultiShadeView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
+7 −0
Original line number Diff line number Diff line
@@ -110,6 +110,13 @@
        android:clipChildren="false"
        android:clipToPadding="false" />

    <ViewStub
        android:id="@+id/multi_shade_stub"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:inflatedId="@+id/multi_shade"
        android:layout="@layout/multi_shade" />

    <com.android.systemui.biometrics.AuthRippleView
        android:id="@+id/auth_ripple"
        android:layout_width="match_parent"
+71 −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.multishade.ui.view

import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
import com.android.systemui.util.time.SystemClock
import kotlinx.coroutines.launch

/**
 * View that hosts the multi-shade system and acts as glue between legacy code and the
 * implementation.
 */
class MultiShadeView(
    context: Context,
    attrs: AttributeSet?,
) :
    FrameLayout(
        context,
        attrs,
    ) {

    fun init(
        interactor: MultiShadeInteractor,
        clock: SystemClock,
    ) {
        repeatWhenAttached {
            lifecycleScope.launch {
                repeatOnLifecycle(Lifecycle.State.CREATED) {
                    addView(
                        ComposeFacade.createMultiShadeView(
                            context = context,
                            viewModel =
                                MultiShadeViewModel(
                                    viewModelScope = this,
                                    interactor = interactor,
                                ),
                            clock = clock,
                        )
                    )
                }

                // Here when destroyed.
                removeAllViews()
            }
        }
    }
}
+23 −4
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import android.app.StatusBarManager;
import android.media.AudioManager;
import android.media.session.MediaSessionLegacyHelper;
import android.os.PowerManager;
import android.os.SystemClock;
import android.util.Log;
import android.view.GestureDetector;
import android.view.InputDevice;
@@ -31,6 +30,7 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;

import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.AuthKeyguardMessageArea;
@@ -39,8 +39,10 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.R;
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -49,6 +51,8 @@ import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
import com.android.systemui.multishade.ui.view.MultiShadeView;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationInsetsController;
@@ -63,11 +67,13 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.util.time.SystemClock;

import java.io.PrintWriter;
import java.util.function.Consumer;

import javax.inject.Inject;
import javax.inject.Provider;

/**
 * Controller for {@link NotificationShadeWindowView}.
@@ -115,6 +121,7 @@ public class NotificationShadeWindowViewController {
                mIsOcclusionTransitionRunning =
                    step.getTransitionState() == TransitionState.RUNNING;
            };
    private final SystemClock mClock;

    @Inject
    public NotificationShadeWindowViewController(
@@ -142,7 +149,9 @@ public class NotificationShadeWindowViewController {
            UdfpsOverlayInteractor udfpsOverlayInteractor,
            KeyguardTransitionInteractor keyguardTransitionInteractor,
            PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
            FeatureFlags featureFlags) {
            FeatureFlags featureFlags,
            Provider<MultiShadeInteractor> multiShadeInteractorProvider,
            SystemClock clock) {
        mLockscreenShadeTransitionController = transitionController;
        mFalsingCollector = falsingCollector;
        mStatusBarStateController = statusBarStateController;
@@ -175,6 +184,16 @@ public class NotificationShadeWindowViewController {

        collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
                mLockscreenToDreamingTransition);

        mClock = clock;
        if (ComposeFacade.INSTANCE.isComposeAvailable()
                && featureFlags.isEnabled(Flags.DUAL_SHADE)) {
            final ViewStub multiShadeViewStub = mView.findViewById(R.id.multi_shade_stub);
            if (multiShadeViewStub != null) {
                final MultiShadeView multiShadeView = (MultiShadeView) multiShadeViewStub.inflate();
                multiShadeView.init(multiShadeInteractorProvider.get(), clock);
            }
        }
    }

    /**
@@ -269,7 +288,7 @@ public class NotificationShadeWindowViewController {
                mLockIconViewController.onTouchEvent(
                        ev,
                        () -> mService.wakeUpIfDozing(
                                SystemClock.uptimeMillis(),
                                mClock.uptimeMillis(),
                                mView,
                                "LOCK_ICON_TOUCH",
                                PowerManager.WAKE_REASON_GESTURE)
@@ -453,7 +472,7 @@ public class NotificationShadeWindowViewController {

    public void cancelCurrentTouch() {
        if (mTouchActive) {
            final long now = SystemClock.uptimeMillis();
            final long now = mClock.uptimeMillis();
            final MotionEvent event;
            if (mIsTrackpadGestureBackEnabled) {
                event = MotionEvent.obtain(mDownEvent);
+154 −112
Original line number Diff line number Diff line
@@ -37,6 +37,9 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
import com.android.systemui.multishade.data.repository.MultiShadeRepository
import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationInsetsController
@@ -50,8 +53,12 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -65,10 +72,12 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
class NotificationShadeWindowViewControllerTest : SysuiTestCase() {

    @Mock private lateinit var view: NotificationShadeWindowView
    @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
    @Mock private lateinit var centralSurfaces: CentralSurfaces
@@ -102,6 +111,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {

    private lateinit var underTest: NotificationShadeWindowViewController

    private lateinit var testScope: TestScope

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
@@ -115,8 +126,12 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
        whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
            .thenReturn(emptyFlow<TransitionStep>())

        val featureFlags = FakeFeatureFlags();
        val featureFlags = FakeFeatureFlags()
        featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false)
        featureFlags.set(Flags.DUAL_SHADE, false)

        val inputProxy = MultiShadeInputProxy()
        testScope = TestScope()
        underTest =
            NotificationShadeWindowViewController(
                lockscreenShadeTransitionController,
@@ -144,6 +159,18 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
                keyguardTransitionInteractor,
                primaryBouncerToGoneTransitionViewModel,
                featureFlags,
                {
                    MultiShadeInteractor(
                        applicationScope = testScope.backgroundScope,
                        repository =
                            MultiShadeRepository(
                                applicationContext = context,
                                inputProxy = inputProxy,
                            ),
                        inputProxy = inputProxy,
                    )
                },
                FakeSystemClock(),
            )
        underTest.setupExpandedStatusBar()

@@ -156,16 +183,18 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
    // tests need to be added to test the rest of handleDispatchTouchEvent.

    @Test
    fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() {
    fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() =
        testScope.runTest {
            underTest.setStatusBarViewController(null)

        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)

            assertThat(returnVal).isFalse()
        }

    @Test
    fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() {
    fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() =
        testScope.runTest {
            underTest.setStatusBarViewController(phoneStatusBarViewController)
            val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
            whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true)
@@ -177,13 +206,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
        }

    @Test
    fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() {
    fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() =
        testScope.runTest {
            underTest.setStatusBarViewController(phoneStatusBarViewController)
            val downEvBelow =
                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
            interactionEventHandler.handleDispatchTouchEvent(downEvBelow)

        val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0)
            val nextEvent =
                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0)
            whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)

            val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
@@ -193,22 +224,24 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
        }

    @Test
    fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() {
    fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() =
        testScope.runTest {
            underTest.setStatusBarViewController(phoneStatusBarViewController)
            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
                .thenReturn(true)
        whenever(phoneStatusBarViewController.sendTouchToView(downEv)).thenReturn(true)
            whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true)

        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)

        verify(phoneStatusBarViewController).sendTouchToView(downEv)
            verify(phoneStatusBarViewController).sendTouchToView(DOWN_EVENT)
            assertThat(returnVal).isTrue()
        }

    @Test
    fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() {
    fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() =
        testScope.runTest {
            underTest.setStatusBarViewController(phoneStatusBarViewController)
            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
@@ -216,14 +249,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
            // Item we're testing
            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false)

        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)

        verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
            verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
            assertThat(returnVal).isNull()
        }

    @Test
    fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() {
    fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() =
        testScope.runTest {
            underTest.setStatusBarViewController(phoneStatusBarViewController)
            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
@@ -231,14 +265,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
                .thenReturn(false)

        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)

        verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
            verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
            assertThat(returnVal).isNull()
        }

    @Test
    fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() {
    fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() =
        testScope.runTest {
            underTest.setStatusBarViewController(phoneStatusBarViewController)
            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
@@ -246,14 +281,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
            // Item we're testing
            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false)

        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)

        verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
            verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
            assertThat(returnVal).isTrue()
        }

    @Test
    fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() {
    fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() =
        testScope.runTest {
            underTest.setStatusBarViewController(phoneStatusBarViewController)
            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
@@ -261,7 +297,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
                .thenReturn(true)

            // Down event first
        interactionEventHandler.handleDispatchTouchEvent(downEv)
            interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)

            // Then another event
            val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
@@ -274,29 +310,35 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
        }

    @Test
    fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() {
    fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() =
        testScope.runTest {
            // Down event within udfpsOverlay bounds while alternateBouncer is showing
        whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(downEv)).thenReturn(false)
            whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(DOWN_EVENT))
                .thenReturn(false)
            whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)

            // Then touch should not be intercepted
        val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(downEv)
            val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)
            assertThat(shouldIntercept).isFalse()
        }

    @Test
    fun testGetBouncerContainer() {
    fun testGetBouncerContainer() =
        testScope.runTest {
            Mockito.clearInvocations(view)
            underTest.bouncerContainer
            verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container)
        }

    @Test
    fun testGetKeyguardMessageArea() {
    fun testGetKeyguardMessageArea() =
        testScope.runTest {
            underTest.keyguardMessageArea
            verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area)
        }
}

private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
    companion object {
        private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
        private const val VIEW_BOTTOM = 100
    }
}
Loading