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

Commit a2cbecbb authored by Chris Göllner's avatar Chris Göllner Committed by Android (Google) Code Review
Browse files

Merge changes Icbe9d247,I5f0b8ddb into main

* changes:
  Show system event chip on connected displays
  Show privacy dot on connected displays
parents b00c0520 83dffac8
Loading
Loading
Loading
Loading
+54 −9
Original line number Diff line number Diff line
@@ -17,11 +17,13 @@
package com.android.systemui.statusbar.core

import android.platform.test.annotations.EnableFlags
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.data.repository.fakePrivacyDotWindowControllerStore
import com.android.systemui.testKosmos
import com.google.common.truth.Expect
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,6 +32,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.never
import org.mockito.kotlin.verify

@OptIn(ExperimentalCoroutinesApi::class)
@@ -39,16 +42,12 @@ import org.mockito.kotlin.verify
class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
    @get:Rule val expect: Expect = Expect.create()

    private val kosmos =
        testKosmos().also {
            it.statusBarOrchestratorFactory = it.fakeStatusBarOrchestratorFactory
            it.statusBarInitializerStore = it.fakeStatusBarInitializerStore
        }
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val fakeDisplayRepository = kosmos.displayRepository
    private val fakeOrchestratorFactory = kosmos.fakeStatusBarOrchestratorFactory
    private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore

    private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore
    // Lazy, so that @EnableFlags is set before initializer is instantiated.
    private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }

@@ -82,6 +81,31 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 2)!!).start()
        }

    @Test
    fun start_startsPrivacyDotForCurrentDisplays() =
        testScope.runTest {
            fakeDisplayRepository.addDisplay(displayId = 1)
            fakeDisplayRepository.addDisplay(displayId = 2)

            underTest.start()
            runCurrent()

            verify(fakePrivacyDotStore.forDisplay(displayId = 1)).start()
            verify(fakePrivacyDotStore.forDisplay(displayId = 2)).start()
        }

    @Test
    fun start_doesNotStartPrivacyDotForDefaultDisplay() =
        testScope.runTest {
            fakeDisplayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)

            underTest.start()
            runCurrent()

            verify(fakePrivacyDotStore.forDisplay(displayId = Display.DEFAULT_DISPLAY), never())
                .start()
        }

    @Test
    fun displayAdded_orchestratorForNewDisplayIsStarted() =
        testScope.runTest {
@@ -108,6 +132,18 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
                .isTrue()
        }

    @Test
    fun displayAdded_privacyDotForNewDisplayIsStarted() =
        testScope.runTest {
            underTest.start()
            runCurrent()

            fakeDisplayRepository.addDisplay(displayId = 3)
            runCurrent()

            verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
        }

    @Test
    fun displayAddedDuringStart_initializerForNewDisplayIsStarted() =
        testScope.runTest {
@@ -129,8 +165,17 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
            fakeDisplayRepository.addDisplay(displayId = 3)
            runCurrent()

            expect
                .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
                .isTrue()
            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
        }

    @Test
    fun displayAddedDuringStart_privacyDotForNewDisplayIsStarted() =
        testScope.runTest {
            underTest.start()

            fakeDisplayRepository.addDisplay(displayId = 3)
            runCurrent()

            verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
        }
}
+54 −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.statusbar.data.repository

import android.platform.test.annotations.EnableFlags
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.testKosmos
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
class PrivacyDotWindowControllerStoreImplTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val underTest by lazy { kosmos.privacyDotWindowControllerStoreImpl }

    @Before
    fun installDisplays() = runBlocking {
        kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
        kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY + 1)
    }

    @Test(expected = IllegalArgumentException::class)
    fun forDisplay_defaultDisplay_throws() {
        underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY)
    }

    @Test
    fun forDisplay_nonDefaultDisplay_doesNotThrow() {
        underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY + 1)
    }
}
+73 −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.statusbar.data.repository

import android.platform.test.annotations.EnableFlags
import android.view.Display.DEFAULT_DISPLAY
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.testKosmos
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.never
import org.mockito.kotlin.verify

@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
class SystemEventChipAnimationControllerStoreImplTest : SysuiTestCase() {

    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val testScope = kosmos.testScope
    private val fakeDisplayRepository = kosmos.displayRepository

    // Lazy so that @EnableFlags has time to run before underTest is instantiated.
    private val underTest by lazy { kosmos.systemEventChipAnimationControllerStoreImpl }

    @Before
    fun start() {
        underTest.start()
    }

    @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }

    @Test
    fun beforeDisplayRemoved_doesNotStopInstances() =
        testScope.runTest {
            val instance = underTest.forDisplay(DEFAULT_DISPLAY)

            verify(instance, never()).stop()
        }

    @Test
    fun displayRemoved_stopsInstance() =
        testScope.runTest {
            val instance = underTest.forDisplay(DEFAULT_DISPLAY)

            fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)

            verify(instance).stop()
        }
}
+108 −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.statusbar.events

import android.platform.test.annotations.EnableFlags
import androidx.core.animation.AnimatorSet
import androidx.core.animation.ValueAnimator
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.repository.systemEventChipAnimationControllerStore
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
class MultiDisplaySystemEventChipAnimationControllerTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val displayRepository = kosmos.displayRepository
    private val store = kosmos.systemEventChipAnimationControllerStore

    // Lazy so that @EnableFlags has time to switch the flags before the instance is created.
    private val underTest by lazy { kosmos.multiDisplaySystemEventChipAnimationController }

    @Before
    fun installDisplays() = runBlocking {
        INSTALLED_DISPLAY_IDS.forEach { displayRepository.addDisplay(displayId = it) }
    }

    @Test
    fun init_forwardsToAllControllers() {
        underTest.init()

        INSTALLED_DISPLAY_IDS.forEach { verify(store.forDisplay(it)).init() }
    }

    @Test
    fun stop_forwardsToAllControllers() {
        underTest.stop()

        INSTALLED_DISPLAY_IDS.forEach { verify(store.forDisplay(it)).stop() }
    }

    @Test
    fun announceForAccessibility_forwardsToAllControllers() {
        val contentDescription = "test content description"
        underTest.announceForAccessibility(contentDescription)

        INSTALLED_DISPLAY_IDS.forEach {
            verify(store.forDisplay(it)).announceForAccessibility(contentDescription)
        }
    }

    @Test
    fun onSystemEventAnimationBegin_returnsAnimatorSetWithOneAnimatorPerDisplay() {
        INSTALLED_DISPLAY_IDS.forEach {
            val controller = store.forDisplay(it)
            whenever(controller.onSystemEventAnimationBegin()).thenReturn(ValueAnimator.ofInt(0, 1))
        }
        val animator = underTest.onSystemEventAnimationBegin() as AnimatorSet

        assertThat(animator.childAnimations).hasSize(INSTALLED_DISPLAY_IDS.size)
    }

    @Test
    fun onSystemEventAnimationFinish_returnsAnimatorSetWithOneAnimatorPerDisplay() {
        INSTALLED_DISPLAY_IDS.forEach {
            val controller = store.forDisplay(it)
            whenever(controller.onSystemEventAnimationFinish(any()))
                .thenReturn(ValueAnimator.ofInt(0, 1))
        }
        val animator =
            underTest.onSystemEventAnimationFinish(hasPersistentDot = true) as AnimatorSet

        assertThat(animator.childAnimations).hasSize(INSTALLED_DISPLAY_IDS.size)
    }

    companion object {
        private const val DISPLAY_ID_1 = 123
        private const val DISPLAY_ID_2 = 456
        private val INSTALLED_DISPLAY_IDS = listOf(DISPLAY_ID_1, DISPLAY_ID_2)
    }
}
+185 −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.statusbar.events

import android.view.Gravity.BOTTOM
import android.view.Gravity.LEFT
import android.view.Gravity.RIGHT
import android.view.Gravity.TOP
import android.view.Surface
import android.view.View
import android.view.WindowManager
import android.view.fakeWindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock

@SmallTest
@RunWith(AndroidJUnit4::class)
class PrivacyDotWindowControllerTest : SysuiTestCase() {

    @get:Rule val expect: Expect = Expect.create()

    private val kosmos = testKosmos()
    private val underTest = kosmos.privacyDotWindowController
    private val viewController = kosmos.privacyDotViewController
    private val windowManager = kosmos.fakeWindowManager
    private val executor = kosmos.fakeExecutor

    @After
    fun cleanUpCustomDisplay() {
        context.display = null
    }

    @Test
    fun start_beforeUiThreadExecutes_doesNotAddWindows() {
        underTest.start()

        assertThat(windowManager.addedViews).isEmpty()
    }

    @Test
    fun start_beforeUiThreadExecutes_doesNotInitializeViewController() {
        underTest.start()

        assertThat(viewController.isInitialized).isFalse()
    }

    @Test
    fun start_afterUiThreadExecutes_addsWindowsOnUiThread() {
        underTest.start()

        executor.runAllReady()

        assertThat(windowManager.addedViews).hasSize(4)
    }

    @Test
    fun start_afterUiThreadExecutes_initializesViewController() {
        underTest.start()

        executor.runAllReady()

        assertThat(viewController.isInitialized).isTrue()
    }

    @Test
    fun start_initializesTopLeft() {
        underTest.start()
        executor.runAllReady()

        assertThat(viewController.topLeft?.id).isEqualTo(R.id.privacy_dot_top_left_container)
    }

    @Test
    fun start_initializesTopRight() {
        underTest.start()
        executor.runAllReady()

        assertThat(viewController.topRight?.id).isEqualTo(R.id.privacy_dot_top_right_container)
    }

    @Test
    fun start_initializesTopBottomLeft() {
        underTest.start()
        executor.runAllReady()

        assertThat(viewController.bottomLeft?.id).isEqualTo(R.id.privacy_dot_bottom_left_container)
    }

    @Test
    fun start_initializesBottomRight() {
        underTest.start()
        executor.runAllReady()

        assertThat(viewController.bottomRight?.id)
            .isEqualTo(R.id.privacy_dot_bottom_right_container)
    }

    @Test
    fun start_viewsAddedInRespectiveCorners() {
        context.display = mock { on { rotation } doReturn Surface.ROTATION_0 }

        underTest.start()
        executor.runAllReady()

        expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or LEFT)
        expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or RIGHT)
        expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or LEFT)
        expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or RIGHT)
    }

    @Test
    fun start_rotation90_viewsPositionIsShifted90degrees() {
        context.display = mock { on { rotation } doReturn Surface.ROTATION_90 }

        underTest.start()
        executor.runAllReady()

        expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or LEFT)
        expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or LEFT)
        expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or RIGHT)
        expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or RIGHT)
    }

    @Test
    fun start_rotation180_viewsPositionIsShifted180degrees() {
        context.display = mock { on { rotation } doReturn Surface.ROTATION_180 }

        underTest.start()
        executor.runAllReady()

        expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or RIGHT)
        expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or LEFT)
        expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or RIGHT)
        expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or LEFT)
    }

    @Test
    fun start_rotation270_viewsPositionIsShifted270degrees() {
        context.display = mock { on { rotation } doReturn Surface.ROTATION_270 }

        underTest.start()
        executor.runAllReady()

        expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or RIGHT)
        expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or RIGHT)
        expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or LEFT)
        expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or LEFT)
    }

    private fun paramsForView(view: View): WindowManager.LayoutParams {
        return windowManager.addedViews.entries
            .first { it.key == view || it.key.findViewById<View>(view.id) != null }
            .value
    }

    private fun gravityForView(view: View): Int {
        return paramsForView(view).gravity
    }
}
Loading