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

Commit 6ae3959f authored by Will Leshner's avatar Will Leshner
Browse files

Allow opening glanceable hub over dream.

Bug: 315194787
Test: atest CommunalTouchHandlerTest
Test: atest CommunalInteractorTest
Flag: ACONFIG com.android.systemui.communal_hub DEVELOPMENT
Change-Id: Ifeff513b38bf9244bfd6ec289da891c0af9e0280
parent a19cb200
Loading
Loading
Loading
Loading
+4 −5
Original line number Diff line number Diff line
@@ -64,12 +64,11 @@ fun CommunalContainer(
            transitions = sceneTransitions,
        )

    // Don't show hub mode UI if keyguard is not present. This is important since we're in the
    // shade, which can be opened from many locations.
    val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false)
    // Don't show hub mode UI if communal is not available. Communal is only available if it has
    // been enabled via settings and either keyguard is showing, or, the device is currently
    // dreaming.
    val isCommunalAvailable by viewModel.isCommunalAvailable.collectAsState()

    if (!isKeyguardShowing || !isCommunalAvailable) {
    if (!isCommunalAvailable) {
        return
    }

+17 −0
Original line number Diff line number Diff line
@@ -124,6 +124,7 @@ class CommunalInteractorTest : SysuiTestCase() {

            keyguardRepository.setIsEncryptedOrLockdown(false)
            userRepository.setSelectedUserInfo(mainUser)
            keyguardRepository.setKeyguardShowing(true)
            runCurrent()

            assertThat(isAvailable).isTrue()
@@ -150,11 +151,26 @@ class CommunalInteractorTest : SysuiTestCase() {

            keyguardRepository.setIsEncryptedOrLockdown(false)
            userRepository.setSelectedUserInfo(secondaryUser)
            keyguardRepository.setKeyguardShowing(true)
            runCurrent()

            assertThat(isAvailable).isFalse()
        }

    @Test
    fun isCommunalAvailable_whenDreaming_true() =
        testScope.runTest {
            val isAvailable by collectLastValue(underTest.isCommunalAvailable)
            assertThat(isAvailable).isFalse()

            keyguardRepository.setIsEncryptedOrLockdown(false)
            userRepository.setSelectedUserInfo(mainUser)
            keyguardRepository.setDreaming(true)
            runCurrent()

            assertThat(isAvailable).isTrue()
        }

    @Test
    fun updateAppWidgetHostActive_uponStorageUnlockAsMainUser_true() =
        testScope.runTest {
@@ -163,6 +179,7 @@ class CommunalInteractorTest : SysuiTestCase() {

            keyguardRepository.setIsEncryptedOrLockdown(false)
            userRepository.setSelectedUserInfo(mainUser)
            keyguardRepository.setKeyguardShowing(true)
            runCurrent()

            assertThat(widgetRepository.isHostActive()).isTrue()
+101 −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 com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.verify;

import android.view.GestureDetector;
import android.view.MotionEvent;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.systemui.SysuiTestCase;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.NotificationShadeWindowController;
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(AndroidJUnit4.class)
public class CommunalTouchHandlerTest extends SysuiTestCase {
    @Mock
    CentralSurfaces mCentralSurfaces;
    @Mock
    NotificationShadeWindowController mNotificationShadeWindowController;
    @Mock
    DreamTouchHandler.TouchSession mTouchSession;
    CommunalTouchHandler mTouchHandler;

    private static final int INITIATION_WIDTH = 20;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mTouchHandler = new CommunalTouchHandler(
                Optional.of(mCentralSurfaces),
                mNotificationShadeWindowController,
                INITIATION_WIDTH);
    }

    @Test
    public void testSessionStartForcesShadeOpen() {
        mTouchHandler.onSessionStart(mTouchSession);
        verify(mNotificationShadeWindowController).setForcePluginOpen(true, mTouchHandler);
    }

    @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(mCentralSurfaces).handleDreamTouch(motionEvent);
    }

    @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();
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -1803,6 +1803,9 @@
    <dimen name="dream_overlay_complication_smartspace_padding">24dp</dimen>
    <dimen name="dream_overlay_complication_smartspace_max_width">408dp</dimen>

    <!-- The width of the swipe target to initiate opening communal hub over dreams. -->
    <dimen name="communal_gesture_initiation_width">48dp</dimen>

    <!-- The position of the end guide, which dream overlay complications can align their start with
         if their end is aligned with the parent end. Represented as the percentage over from the
         start of the parent container. -->
+7 −7
Original line number Diff line number Diff line
@@ -68,21 +68,23 @@ constructor(
    private val appWidgetHost: CommunalAppWidgetHost,
    private val editWidgetsActivityStarter: EditWidgetsActivityStarter
) {

    /** Whether communal features are enabled. */
    val isCommunalEnabled: Boolean
        get() = communalRepository.isCommunalEnabled

    /** Whether communal features are enabled and available. */
    val isCommunalAvailable: StateFlow<Boolean> =
    val isCommunalAvailable =
        flowOf(isCommunalEnabled)
            .flatMapLatest { enabled ->
                if (enabled)
                    combine(
                        keyguardInteractor.isEncryptedOrLockdown,
                        userRepository.selectedUserInfo,
                    ) { isEncryptedOrLockdown, selectedUserInfo ->
                        !isEncryptedOrLockdown && selectedUserInfo.isMain
                        keyguardInteractor.isKeyguardVisible,
                        keyguardInteractor.isDreaming,
                    ) { isEncryptedOrLockdown, selectedUserInfo, isKeyguardVisible, isDreaming ->
                        !isEncryptedOrLockdown &&
                            selectedUserInfo.isMain &&
                            (isKeyguardVisible || isDreaming)
                    }
                else flowOf(false)
            }
@@ -154,8 +156,6 @@ constructor(
            it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Communal
        }

    val isKeyguardVisible: Flow<Boolean> = keyguardInteractor.isKeyguardVisible

    /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
    fun onSceneChanged(newScene: CommunalSceneKey) {
        communalRepository.setDesiredScene(newScene)
Loading