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 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 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 Diff line number Diff line
@@ -17,13 +17,16 @@
package com.android.systemui.media.controls.ui.view

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

    private val carouselWidth = 1038
+0 −168
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.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