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

Commit ea1e8487 authored by Michael Mikhail's avatar Michael Mikhail
Browse files

Add media common view model DiffUtil classes

Flag: ACONFIG com.android.systemui.media_controls_refactor DISABLED
Bug: 328207006
Test: atest SystemUiRoboTest:MediaDiffUtilTest
Change-Id: I291aa25f9079efd5bab33d9f30c9cfeac0e502e4
parent 4960c01d
Loading
Loading
Loading
Loading
+226 −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.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.MediaCommonViewModel
import com.android.systemui.media.controls.ui.viewmodel.mediaControlViewModel
import com.android.systemui.media.controls.ui.viewmodel.mediaRecommendationsViewModel
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), true)
        val oldList = listOf<MediaCommonViewModel>()
        val newList = listOf(mediaControl)
        val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
        val mediaLoadedListUpdateCallback =
            MediaViewModelListUpdateCallback(
                oldList,
                newList,
                { commonViewModel -> assertThat(commonViewModel).isEqualTo(mediaControl) },
                { fail("Unexpected to update $it") },
                { fail("Unexpected to remove $it") },
                { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") },
            )

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

    @Test
    fun newMediaRecommendationsAdded() {
        val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE, true)
        val oldList = listOf<MediaCommonViewModel>()
        val newList = listOf(mediaRecs)
        val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
        val mediaLoadedListUpdateCallback =
            MediaViewModelListUpdateCallback(
                oldList,
                newList,
                { commonViewModel -> assertThat(commonViewModel).isEqualTo(mediaRecs) },
                { fail("Unexpected to update $it") },
                { 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), true)
        val oldList = listOf(mediaControl)
        val newList = listOf(mediaControl.copy(immediatelyUpdateUi = false))
        val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
        val mediaLoadedListUpdateCallback =
            MediaViewModelListUpdateCallback(
                oldList,
                newList,
                { fail("Unexpected to add $it") },
                { commonViewModel -> assertThat(commonViewModel).isNotEqualTo(mediaControl) },
                { fail("Unexpected to remove $it") },
                { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") },
            )

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

    @Test
    fun updateMediaRecommendations_contentChanged() {
        val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE, true)
        val oldList = listOf(mediaRecs)
        val newList = listOf(mediaRecs.copy(key = KEY_MEDIA_SMARTSPACE_2))
        val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
        val mediaLoadedListUpdateCallback =
            MediaViewModelListUpdateCallback(
                oldList,
                newList,
                { fail("Unexpected to add $it") },
                { commonViewModel -> assertThat(commonViewModel).isNotEqualTo(mediaRecs) },
                { fail("Unexpected to remove $it") },
                { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") },
            )

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

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

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

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

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

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

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

    @Test
    fun mediaRecommendationsRemoved() {
        val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE_2, false)
        val oldList = listOf(mediaRecs)
        val newList = listOf<MediaCommonViewModel>()
        val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
        val mediaLoadedListUpdateCallback =
            MediaViewModelListUpdateCallback(
                oldList,
                newList,
                { fail("Unexpected to add $it") },
                { fail("Unexpected to update $it") },
                { commonViewModel -> assertThat(commonViewModel).isEqualTo(mediaRecs) },
                { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") },
            )

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

    private fun createMediaControl(
        instanceId: InstanceId,
        immediatelyUpdateUi: Boolean,
    ): MediaCommonViewModel.MediaControl {
        return MediaCommonViewModel.MediaControl(
            instanceId = instanceId,
            immediatelyUpdateUi = immediatelyUpdateUi,
            controlViewModel = kosmos.mediaControlViewModel,
            onAdded = {},
            onRemoved = { _, _ -> },
            onUpdated = {}
        )
    }

    private fun createMediaRecommendations(
        key: String,
        loadingEnabled: Boolean,
    ): MediaCommonViewModel.MediaRecommendations {
        return MediaCommonViewModel.MediaRecommendations(
            key = key,
            loadingEnabled = loadingEnabled,
            recsViewModel = kosmos.mediaRecommendationsViewModel,
            onAdded = {},
            onRemoved = { _, _ -> },
            onUpdated = {}
        )
    }

    companion object {
        private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
        private const val KEY_MEDIA_SMARTSPACE_2 = "MEDIA_SMARTSPACE_ID_2"
    }
}
+67 −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.media.controls.ui.util

import androidx.recyclerview.widget.DiffUtil
import com.android.systemui.media.controls.ui.viewmodel.MediaCommonViewModel

/** A [DiffUtil.Callback] to calculate difference between old and new media view-model list. */
class MediaViewModelCallback(
    private val old: List<MediaCommonViewModel>,
    private val new: List<MediaCommonViewModel>,
) : DiffUtil.Callback() {

    override fun getOldListSize(): Int {
        return old.size
    }

    override fun getNewListSize(): Int {
        return new.size
    }

    override fun areItemsTheSame(oldIndex: Int, newIndex: Int): Boolean {
        val oldItem = old[oldIndex]
        val newItem = new[newIndex]
        return if (
            oldItem is MediaCommonViewModel.MediaControl &&
                newItem is MediaCommonViewModel.MediaControl
        ) {
            oldItem.instanceId == newItem.instanceId
        } else {
            oldItem is MediaCommonViewModel.MediaRecommendations &&
                newItem is MediaCommonViewModel.MediaRecommendations
        }
    }

    override fun areContentsTheSame(oldIndex: Int, newIndex: Int): Boolean {
        val oldItem = old[oldIndex]
        val newItem = new[newIndex]
        return if (
            oldItem is MediaCommonViewModel.MediaControl &&
                newItem is MediaCommonViewModel.MediaControl
        ) {
            oldItem.immediatelyUpdateUi == newItem.immediatelyUpdateUi
        } else if (
            oldItem is MediaCommonViewModel.MediaRecommendations &&
                newItem is MediaCommonViewModel.MediaRecommendations
        ) {
            oldItem.key == newItem.key && oldItem.loadingEnabled == newItem.loadingEnabled
        } else {
            false
        }
    }
}
+53 −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.media.controls.ui.util

import androidx.recyclerview.widget.ListUpdateCallback
import com.android.systemui.media.controls.ui.viewmodel.MediaCommonViewModel

/** A [ListUpdateCallback] to apply media events needed to reach the new state. */
class MediaViewModelListUpdateCallback(
    private val old: List<MediaCommonViewModel>,
    private val new: List<MediaCommonViewModel>,
    private val onAdded: (MediaCommonViewModel) -> Unit,
    private val onUpdated: (MediaCommonViewModel) -> Unit,
    private val onRemoved: (MediaCommonViewModel) -> Unit,
    private val onMoved: (MediaCommonViewModel, Int, Int) -> Unit,
) : ListUpdateCallback {

    override fun onInserted(position: Int, count: Int) {
        for (i in position until position + count) {
            onAdded(new[i])
        }
    }

    override fun onRemoved(position: Int, count: Int) {
        for (i in position until position + count) {
            onRemoved(old[i])
        }
    }

    override fun onMoved(fromPosition: Int, toPosition: Int) {
        onMoved(old[fromPosition], fromPosition, toPosition)
    }

    override fun onChanged(position: Int, count: Int, payload: Any?) {
        for (i in position until position + count) {
            onUpdated(new[i])
        }
    }
}