1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.media.controls.ui 18 19 import android.content.res.Configuration 20 import android.content.res.Configuration.ORIENTATION_LANDSCAPE 21 import android.testing.AndroidTestingRunner 22 import android.testing.TestableLooper 23 import android.view.View 24 import androidx.test.filters.SmallTest 25 import com.android.systemui.R 26 import com.android.systemui.SysuiTestCase 27 import com.android.systemui.media.controls.models.player.MediaViewHolder 28 import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder 29 import com.android.systemui.media.controls.util.MediaFlags 30 import com.android.systemui.util.animation.MeasurementInput 31 import com.android.systemui.util.animation.TransitionLayout 32 import com.android.systemui.util.animation.TransitionViewState 33 import com.android.systemui.util.animation.WidgetState 34 import junit.framework.Assert.assertTrue 35 import org.junit.Before 36 import org.junit.Test 37 import org.junit.runner.RunWith 38 import org.mockito.ArgumentMatchers.floatThat 39 import org.mockito.Mock 40 import org.mockito.Mockito.times 41 import org.mockito.Mockito.verify 42 import org.mockito.Mockito.`when` as whenever 43 import org.mockito.MockitoAnnotations 44 45 @SmallTest 46 @TestableLooper.RunWithLooper(setAsMainLooper = true) 47 @RunWith(AndroidTestingRunner::class) 48 class MediaViewControllerTest : SysuiTestCase() { 49 private val mediaHostStateHolder = MediaHost.MediaHostStateHolder() 50 private val mediaHostStatesManager = MediaHostStatesManager() 51 private val configurationController = 52 com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context) 53 private var player = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0) 54 private var recommendation = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0) 55 @Mock lateinit var logger: MediaViewLogger 56 @Mock private lateinit var mockViewState: TransitionViewState 57 @Mock private lateinit var mockCopiedState: TransitionViewState 58 @Mock private lateinit var detailWidgetState: WidgetState 59 @Mock private lateinit var controlWidgetState: WidgetState 60 @Mock private lateinit var mediaTitleWidgetState: WidgetState 61 @Mock private lateinit var mediaSubTitleWidgetState: WidgetState 62 @Mock private lateinit var mediaContainerWidgetState: WidgetState 63 @Mock private lateinit var mediaFlags: MediaFlags 64 65 private val delta = 0.1F 66 67 private lateinit var mediaViewController: MediaViewController 68 69 @Before 70 fun setup() { 71 MockitoAnnotations.initMocks(this) 72 mediaViewController = 73 MediaViewController( 74 context, 75 configurationController, 76 mediaHostStatesManager, 77 logger, 78 mediaFlags, 79 ) 80 } 81 82 @Test 83 fun testOrientationChanged_heightOfPlayerIsUpdated() { 84 val newConfig = Configuration() 85 86 mediaViewController.attach(player, MediaViewController.TYPE.PLAYER) 87 // Change the height to see the effect of orientation change. 88 MediaViewHolder.backgroundIds.forEach { id -> 89 mediaViewController.expandedLayout.getConstraint(id).layout.mHeight = 10 90 } 91 newConfig.orientation = ORIENTATION_LANDSCAPE 92 configurationController.onConfigurationChanged(newConfig) 93 94 MediaViewHolder.backgroundIds.forEach { id -> 95 assertTrue( 96 mediaViewController.expandedLayout.getConstraint(id).layout.mHeight == 97 context.resources.getDimensionPixelSize( 98 R.dimen.qs_media_session_height_expanded 99 ) 100 ) 101 } 102 } 103 104 @Test 105 fun testOrientationChanged_heightOfRecCardIsUpdated() { 106 val newConfig = Configuration() 107 108 mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION) 109 // Change the height to see the effect of orientation change. 110 mediaViewController.expandedLayout 111 .getConstraint(RecommendationViewHolder.backgroundId) 112 .layout 113 .mHeight = 10 114 newConfig.orientation = ORIENTATION_LANDSCAPE 115 configurationController.onConfigurationChanged(newConfig) 116 117 assertTrue( 118 mediaViewController.expandedLayout 119 .getConstraint(RecommendationViewHolder.backgroundId) 120 .layout 121 .mHeight == 122 context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded) 123 ) 124 } 125 126 @Test 127 fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() { 128 mediaViewController.attach(player, MediaViewController.TYPE.PLAYER) 129 player.measureState = 130 TransitionViewState().apply { 131 this.height = 100 132 this.measureHeight = 100 133 } 134 mediaHostStateHolder.expansion = 1f 135 val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) 136 val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) 137 mediaHostStateHolder.measurementInput = 138 MeasurementInput(widthMeasureSpec, heightMeasureSpec) 139 140 // Test no squish 141 mediaHostStateHolder.squishFraction = 1f 142 assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100) 143 assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.measureHeight == 100) 144 145 // Test half squish 146 mediaHostStateHolder.squishFraction = 0.5f 147 assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50) 148 assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.measureHeight == 100) 149 } 150 151 @Test 152 fun testObtainViewState_applySquishFraction_toRecommendationTransitionViewState_height() { 153 mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION) 154 recommendation.measureState = TransitionViewState().apply { this.height = 100 } 155 mediaHostStateHolder.expansion = 1f 156 val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) 157 val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) 158 mediaHostStateHolder.measurementInput = 159 MeasurementInput(widthMeasureSpec, heightMeasureSpec) 160 161 // Test no squish 162 mediaHostStateHolder.squishFraction = 1f 163 assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100) 164 assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.measureHeight == 100) 165 166 // Test half squish 167 mediaHostStateHolder.squishFraction = 0.5f 168 assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50) 169 assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.measureHeight == 100) 170 } 171 172 @Test 173 fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forMediaPlayer() { 174 whenever(mockViewState.copy()).thenReturn(mockCopiedState) 175 whenever(mockCopiedState.widgetStates) 176 .thenReturn( 177 mutableMapOf( 178 R.id.media_progress_bar to controlWidgetState, 179 R.id.header_artist to detailWidgetState 180 ) 181 ) 182 whenever(mockCopiedState.measureHeight).thenReturn(200) 183 // detail widgets occupy [90, 100] 184 whenever(detailWidgetState.y).thenReturn(90F) 185 whenever(detailWidgetState.height).thenReturn(10) 186 // control widgets occupy [150, 170] 187 whenever(controlWidgetState.y).thenReturn(150F) 188 whenever(controlWidgetState.height).thenReturn(20) 189 // in current beizer, when the progress reach 0.38, the result will be 0.5 190 mediaViewController.squishViewState(mockViewState, 181.4F / 200F) 191 verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } 192 verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } 193 mediaViewController.squishViewState(mockViewState, 200F / 200F) 194 verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } 195 verify(detailWidgetState, times(2)).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } 196 } 197 198 @Test 199 fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forRecommendation() { 200 whenever(mockViewState.copy()).thenReturn(mockCopiedState) 201 whenever(mockCopiedState.widgetStates) 202 .thenReturn( 203 mutableMapOf( 204 R.id.media_title to mediaTitleWidgetState, 205 R.id.media_subtitle to mediaSubTitleWidgetState, 206 R.id.media_cover1_container to mediaContainerWidgetState 207 ) 208 ) 209 whenever(mockCopiedState.measureHeight).thenReturn(360) 210 // media container widgets occupy [20, 300] 211 whenever(mediaContainerWidgetState.y).thenReturn(20F) 212 whenever(mediaContainerWidgetState.height).thenReturn(280) 213 // media title widgets occupy [320, 330] 214 whenever(mediaTitleWidgetState.y).thenReturn(320F) 215 whenever(mediaTitleWidgetState.height).thenReturn(10) 216 // media subtitle widgets occupy [340, 350] 217 whenever(mediaSubTitleWidgetState.y).thenReturn(340F) 218 whenever(mediaSubTitleWidgetState.height).thenReturn(10) 219 220 // in current beizer, when the progress reach 0.38, the result will be 0.5 221 mediaViewController.squishViewState(mockViewState, 307.6F / 360F) 222 verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } 223 mediaViewController.squishViewState(mockViewState, 320F / 360F) 224 verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } 225 // media title and media subtitle are in same widget group, should be calculate together and 226 // have same alpha 227 mediaViewController.squishViewState(mockViewState, 353.8F / 360F) 228 verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } 229 verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } 230 mediaViewController.squishViewState(mockViewState, 360F / 360F) 231 verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } 232 verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } 233 } 234 } 235