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

Commit cd66423b authored by Michael Mikhail's avatar Michael Mikhail Committed by Android (Google) Code Review
Browse files

Merge changes from topic "move-fields-to-mcp" into main

* changes:
  [Flexiglass] remove unnecessary code in domain layer of media.controls package
  [Flexiglass] remove unnecessary view-models in media.controls package
  [Flexiglass] remove unused helper classes
  [Flexiglass] Clean media view controller code
  [Flexiglass] Clean code in MediaCarouselController
parents 17425e03 8946b03a
Loading
Loading
Loading
Loading
+0 −239
Original line number Original line 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.media.controls.domain.interactor

import android.app.PendingIntent
import android.os.Bundle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.activityIntentHelper
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.data.repository.mediaDataRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaControlInteractor
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.util.mediaInstanceId
import com.android.systemui.media.mediaOutputDialogManager
import com.android.systemui.mockActivityIntentHelper
import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.statusbar.policy.keyguardStateController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

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

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    private val mediaDataFilter: MediaDataFilterImpl = with(kosmos) { mediaDataFilter }
    private val activityStarter = kosmos.activityStarter
    private val keyguardStateController = kosmos.keyguardStateController
    private val instanceId: InstanceId = kosmos.mediaInstanceId
    private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager

    private val underTest: MediaControlInteractor =
        with(kosmos) {
            activityIntentHelper = mockActivityIntentHelper
            kosmos.mediaControlInteractor
        }

    @Test
    fun onMediaDataUpdated() =
        testScope.runTest {
            whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
            whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
            val controlModel by collectLastValue(underTest.mediaControl)
            var mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST)

            mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)

            assertThat(controlModel?.instanceId).isEqualTo(instanceId)
            assertThat(controlModel?.artistName).isEqualTo(ARTIST)

            mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST_2)

            mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)

            assertThat(controlModel?.instanceId).isEqualTo(instanceId)
            assertThat(controlModel?.artistName).isEqualTo(ARTIST_2)

            mediaData =
                MediaData(
                    userId = USER_ID,
                    instanceId = InstanceId.fakeInstanceId(2),
                    artist = ARTIST,
                )

            mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)

            assertThat(controlModel?.instanceId).isNotEqualTo(mediaData.instanceId)
            assertThat(controlModel?.artistName).isEqualTo(ARTIST_2)
        }

    @Test
    fun startSettings() {
        underTest.startSettings()

        verify(activityStarter).startActivity(any(), eq(true))
    }

    @Test
    fun startClickIntent_showOverLockscreen() {
        whenever(keyguardStateController.isShowing).thenReturn(true)
        whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any()))
            .thenReturn(true)

        val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
        val expandable = mock<Expandable>()
        val activityController = mock<ActivityTransitionAnimator.Controller>()
        whenever(expandable.activityTransitionController(any())).thenReturn(activityController)

        underTest.startClickIntent(expandable, clickIntent)

        verify(activityStarter)
            .startPendingIntentMaybeDismissingKeyguard(
                eq(clickIntent),
                eq(null),
                eq(activityController),
            )
    }

    @Test
    fun startClickIntent_hideOverLockscreen() {
        whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
        whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
        whenever(keyguardStateController.isShowing).thenReturn(false)

        val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
        val expandable = mock<Expandable>()
        val activityController = mock<ActivityTransitionAnimator.Controller>()
        whenever(expandable.activityTransitionController(any())).thenReturn(activityController)

        val mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST)
        mediaDataFilter.onMediaDataLoaded(KEY, null, mediaData)
        underTest.startClickIntent(expandable, clickIntent)

        verify(activityStarter)
            .postStartActivityDismissingKeyguard(eq(clickIntent), eq(activityController))
    }

    @Test
    fun startDeviceIntent_showOverLockscreen() {
        whenever(keyguardStateController.isShowing).thenReturn(true)
        whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any()))
            .thenReturn(true)

        val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }

        underTest.startDeviceIntent(deviceIntent)

        verify(deviceIntent).send(any<Bundle>())
    }

    @Test
    fun startDeviceIntent_intentNotActivity() {
        whenever(keyguardStateController.isShowing).thenReturn(true)
        whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any()))
            .thenReturn(true)

        val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(false) }

        underTest.startDeviceIntent(deviceIntent)

        verify(deviceIntent, never()).send(any<Bundle>())
    }

    @Test
    fun startDeviceIntent_hideOverLockscreen() {
        whenever(keyguardStateController.isShowing).thenReturn(false)

        val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }

        underTest.startDeviceIntent(deviceIntent)

        verify(activityStarter).postStartActivityDismissingKeyguard(eq(deviceIntent))
    }

    @Test
    fun startMediaOutputDialog() {
        val expandable = mock<Expandable>()
        val dialogTransitionController = mock<DialogTransitionAnimator.Controller>()
        whenever(expandable.dialogTransitionController(any()))
            .thenReturn(dialogTransitionController)

        underTest.startMediaOutputDialog(expandable, PACKAGE_NAME)

        verify(kosmos.mediaOutputDialogManager)
            .createAndShowWithController(
                eq(PACKAGE_NAME),
                eq(true),
                eq(dialogTransitionController),
                eq(null),
                eq(null),
            )
    }

    @Test
    fun removeMediaControl() {
        whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
        whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
        val listener = mock<MediaDataProcessor.Listener>()
        kosmos.mediaDataProcessor.addInternalListener(listener)

        val mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST)
        kosmos.mediaDataRepository.addMediaEntry(KEY, mediaData)
        kosmos.mediaDataFilter.onMediaDataLoaded(KEY, null, mediaData)

        underTest.removeMediaControl(null, instanceId, 0L)
        kosmos.fakeExecutor.advanceClockToNext()
        kosmos.fakeExecutor.runAllReady()

        verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
    }

    companion object {
        private const val USER_ID = 0
        private const val KEY = "key"
        private const val PACKAGE_NAME = "com.example.app"
        private const val APP_NAME = "app"
        private const val ARTIST = "artist"
        private const val ARTIST_2 = "artist2"
    }
}
+0 −118
Original line number Original line 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.media.controls.ui.util

import androidx.recyclerview.widget.DiffUtil
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
import com.android.systemui.media.controls.ui.viewmodel.mediaControlViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith

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

    private val kosmos = testKosmos()

    @Test
    fun newMediaControlAdded() {
        val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123))
        val oldList = listOf<MediaControlViewModel>()
        val newList = listOf(mediaControl)
        val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
        val mediaLoadedListUpdateCallback =
            MediaViewModelListUpdateCallback(
                oldList,
                newList,
                { commonViewModel, _ -> assertThat(commonViewModel).isEqualTo(mediaControl) },
                { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") },
                { fail("Unexpected to remove $it") },
                { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") },
            )

        DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
    }

    @Test
    fun updateMediaControl_contentChanged() {
        val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123))
        val oldList = listOf(mediaControl)
        val newList = listOf(mediaControl.copy())
        val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
        val mediaLoadedListUpdateCallback =
            MediaViewModelListUpdateCallback(
                oldList,
                newList,
                { controlViewModel, _ -> fail("Unexpected to add $controlViewModel") },
                { controlViewModel, _ -> assertThat(controlViewModel).isNotEqualTo(mediaControl) },
                { fail("Unexpected to remove $it") },
                { controlViewModel, _, _ -> fail("Unexpected to move $controlViewModel ") },
            )

        DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
    }

    @Test
    fun mediaControlMoved() {
        val mediaControl1 = createMediaControl(InstanceId.fakeInstanceId(123))
        val mediaControl2 = createMediaControl(InstanceId.fakeInstanceId(456))
        val oldList = listOf(mediaControl1, mediaControl2)
        val newList = listOf(mediaControl2, mediaControl1)
        val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
        val mediaLoadedListUpdateCallback =
            MediaViewModelListUpdateCallback(
                oldList,
                newList,
                { controlViewModel, _ -> fail("Unexpected to add $controlViewModel") },
                { controlViewModel, _ -> fail("Unexpected to update $controlViewModel") },
                { fail("Unexpected to remove $it") },
                { controlViewModel, _, _ -> assertThat(controlViewModel).isEqualTo(mediaControl1) },
            )

        DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
    }

    @Test
    fun mediaControlRemoved() {
        val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123))
        val oldList = listOf(mediaControl)
        val newList = listOf<MediaControlViewModel>()
        val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
        val mediaLoadedListUpdateCallback =
            MediaViewModelListUpdateCallback(
                oldList,
                newList,
                { controlViewModel, _ -> fail("Unexpected to add $controlViewModel") },
                { controlViewModel, _ -> fail("Unexpected to update $controlViewModel") },
                { controlViewModel -> assertThat(controlViewModel).isEqualTo(mediaControl) },
                { controlViewModel, _, _ -> fail("Unexpected to move $controlViewModel ") },
            )

        DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
    }

    private fun createMediaControl(instanceId: InstanceId): MediaControlViewModel {
        return kosmos.mediaControlViewModel.copy(instanceId = instanceId)
    }
}
+5 −0
Original line number Original line Diff line number Diff line
@@ -17,13 +17,16 @@
package com.android.systemui.media.controls.ui.view
package com.android.systemui.media.controls.ui.view


import android.content.res.Resources
import android.content.res.Resources
import android.platform.test.annotations.DisableFlags
import android.testing.TestableLooper
import android.testing.TestableLooper
import android.view.MotionEvent
import android.view.MotionEvent
import android.view.View
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
import com.android.systemui.qs.PageIndicator
@@ -48,6 +51,8 @@ import org.mockito.kotlin.whenever
@SmallTest
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
@RunWith(AndroidJUnit4::class)
@DisableSceneContainer
@DisableFlags(Flags.FLAG_MEDIA_CONTROLS_IN_COMPOSE)
class MediaCarouselScrollHandlerTest : SysuiTestCase() {
class MediaCarouselScrollHandlerTest : SysuiTestCase() {


    private val carouselWidth = 1038
    private val carouselWidth = 1038
+0 −168
Original line number Original line 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.media.controls.ui.viewmodel

import android.R
import android.content.packageManager
import android.content.pm.ApplicationInfo
import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.controls.shared.mockMediaLogger
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
import org.mockito.Mockito
import org.mockito.kotlin.eq
import org.mockito.kotlin.never
import org.mockito.kotlin.reset
import org.mockito.kotlin.verify

@SmallTest
@RunWith(AndroidJUnit4::class)
@DisableFlags(Flags.FLAG_MEDIA_CONTROLS_IN_COMPOSE)
@DisableSceneContainer
class MediaCarouselViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmos().apply { mediaLogger = mockMediaLogger }
    private val testScope = kosmos.testScope

    private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
    private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
    private val packageManager = kosmos.packageManager
    private val drawable = context.getDrawable(R.drawable.ic_media_play)

    private val underTest: MediaCarouselViewModel = kosmos.mediaCarouselViewModel

    @Before
    fun setUp() {
        kosmos.mediaCarouselInteractor.start()

        whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable)
        whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
            .thenReturn(drawable)
        whenever(packageManager.getApplicationInfo(eq(PACKAGE_NAME), ArgumentMatchers.anyInt()))
            .thenReturn(ApplicationInfo())
        whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)

        context.setMockPackageManager(packageManager)
    }

    @Test
    fun loadMediaControls_mediaItemsAreUpdated() =
        testScope.runTest {
            val sortedMedia by collectLastValue(underTest.mediaItems)
            val instanceId1 = InstanceId.fakeInstanceId(123)
            val instanceId2 = InstanceId.fakeInstanceId(456)

            loadMediaControl(KEY, instanceId1, isPlaying = true)
            loadMediaControl(KEY_2, instanceId2, isPlaying = true)
            loadMediaControl(KEY, instanceId1, isPlaying = false)

            var mediaControl2 = sortedMedia?.get(0) as MediaControlViewModel
            var mediaControl1 = sortedMedia?.get(1) as MediaControlViewModel
            assertThat(mediaControl2.instanceId).isEqualTo(instanceId2)
            assertThat(mediaControl1.instanceId).isEqualTo(instanceId1)

            loadMediaControl(KEY, instanceId1, isPlaying = true)
            loadMediaControl(KEY_2, instanceId2, isPlaying = false)

            mediaControl2 = sortedMedia?.get(0) as MediaControlViewModel
            mediaControl1 = sortedMedia?.get(1) as MediaControlViewModel
            assertThat(mediaControl2.instanceId).isEqualTo(instanceId2)
            assertThat(mediaControl1.instanceId).isEqualTo(instanceId1)

            underTest.onReorderingAllowed()

            mediaControl1 = sortedMedia?.get(0) as MediaControlViewModel
            mediaControl2 = sortedMedia?.get(1) as MediaControlViewModel
            assertThat(mediaControl1.instanceId).isEqualTo(instanceId1)
            assertThat(mediaControl2.instanceId).isEqualTo(instanceId2)
        }

    @Test
    fun addMediaControlThenRemove_mediaEventsAreLogged() =
        testScope.runTest {
            val sortedMedia by collectLastValue(underTest.mediaItems)
            val instanceId = InstanceId.fakeInstanceId(123)

            loadMediaControl(KEY, instanceId)

            val mediaControl = sortedMedia?.get(0) as MediaControlViewModel
            assertThat(mediaControl.instanceId).isEqualTo(instanceId)

            // when media control is added to carousel
            mediaControl.onAdded(mediaControl)

            verify(kosmos.mediaLogger).logMediaCardAdded(eq(instanceId))

            reset(kosmos.mediaLogger)

            // when media control is updated.
            mediaControl.onUpdated(mediaControl)

            verify(kosmos.mediaLogger, never()).logMediaCardAdded(eq(instanceId))

            mediaDataFilter.onMediaDataRemoved(KEY, true)
            assertThat(sortedMedia).isEmpty()

            // when media control is removed from carousel
            mediaControl.onRemoved(true)

            verify(kosmos.mediaLogger).logMediaCardRemoved(eq(instanceId))
        }

    private fun loadMediaControl(key: String, instanceId: InstanceId, isPlaying: Boolean = true) {
        whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
        whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
        val mediaData =
            MediaData(
                userId = USER_ID,
                packageName = PACKAGE_NAME,
                notificationKey = key,
                instanceId = instanceId,
                isPlaying = isPlaying,
            )

        mediaDataFilter.onMediaDataLoaded(key, key, mediaData)
    }

    companion object {
        private const val USER_ID = 0
        private const val KEY = "key"
        private const val KEY_2 = "key2"
        private const val PACKAGE_NAME = "com.example.app"
    }
}
+0 −346

File deleted.

Preview size limit exceeded, changes collapsed.

Loading