1 /*
2  * Copyright (C) 2020 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.animation
18 
19 import android.animation.AnimatorListenerAdapter
20 import android.animation.ValueAnimator
21 import android.graphics.Typeface
22 import android.text.Layout
23 import android.text.StaticLayout
24 import android.text.TextPaint
25 import androidx.test.ext.junit.runners.AndroidJUnit4
26 import androidx.test.filters.SmallTest
27 import com.android.systemui.SysuiTestCase
28 import com.google.common.truth.Truth.assertThat
29 import kotlin.math.ceil
30 import org.junit.Test
31 import org.junit.runner.RunWith
32 import org.mockito.ArgumentCaptor
33 import org.mockito.Mockito.eq
34 import org.mockito.Mockito.inOrder
35 import org.mockito.Mockito.mock
36 import org.mockito.Mockito.never
37 import org.mockito.Mockito.times
38 import org.mockito.Mockito.verify
39 import org.mockito.Mockito.`when`
40 
41 @RunWith(AndroidJUnit4::class)
42 @SmallTest
43 class TextAnimatorTest : SysuiTestCase() {
44 
makeLayoutnull45     private fun makeLayout(text: String, paint: TextPaint): Layout {
46         val width = ceil(Layout.getDesiredWidth(text, 0, text.length, paint)).toInt()
47         return StaticLayout.Builder.obtain(text, 0, text.length, paint, width).build()
48     }
49 
50     @Test
testAnimationStartednull51     fun testAnimationStarted() {
52         val layout = makeLayout("Hello, World", PAINT)
53         val valueAnimator = mock(ValueAnimator::class.java)
54         val textInterpolator = mock(TextInterpolator::class.java)
55         val paint = mock(TextPaint::class.java)
56         `when`(textInterpolator.targetPaint).thenReturn(paint)
57 
58         val textAnimator =
59             TextAnimator(layout, null, {}).apply {
60                 this.textInterpolator = textInterpolator
61                 this.animator = valueAnimator
62             }
63 
64         textAnimator.setTextStyle(weight = 400, animate = true)
65 
66         // If animation is requested, the base state should be rebased and the target state should
67         // be updated.
68         val order = inOrder(textInterpolator)
69         order.verify(textInterpolator).rebase()
70         order.verify(textInterpolator).onTargetPaintModified()
71 
72         // In case of animation, should not shape the base state since the animation should start
73         // from current state.
74         verify(textInterpolator, never()).onBasePaintModified()
75 
76         // Then, animation should be started.
77         verify(valueAnimator, times(1)).start()
78     }
79 
80     @Test
testAnimationNotStartednull81     fun testAnimationNotStarted() {
82         val layout = makeLayout("Hello, World", PAINT)
83         val valueAnimator = mock(ValueAnimator::class.java)
84         val textInterpolator = mock(TextInterpolator::class.java)
85         val paint = mock(TextPaint::class.java)
86         `when`(textInterpolator.targetPaint).thenReturn(paint)
87 
88         val textAnimator =
89             TextAnimator(layout, null, {}).apply {
90                 this.textInterpolator = textInterpolator
91                 this.animator = valueAnimator
92             }
93 
94         textAnimator.setTextStyle(weight = 400, animate = false)
95 
96         // If animation is not requested, the progress should be 1 which is end of animation and the
97         // base state is rebased to target state by calling rebase.
98         val order = inOrder(textInterpolator)
99         order.verify(textInterpolator).onTargetPaintModified()
100         order.verify(textInterpolator).progress = 1f
101         order.verify(textInterpolator).rebase()
102 
103         // Then, animation start should not be called.
104         verify(valueAnimator, never()).start()
105     }
106 
107     @Test
testAnimationEndednull108     fun testAnimationEnded() {
109         val layout = makeLayout("Hello, World", PAINT)
110         val valueAnimator = mock(ValueAnimator::class.java)
111         val textInterpolator = mock(TextInterpolator::class.java)
112         val paint = mock(TextPaint::class.java)
113         `when`(textInterpolator.targetPaint).thenReturn(paint)
114         val animationEndCallback = mock(Runnable::class.java)
115 
116         val textAnimator =
117             TextAnimator(layout, null, {}).apply {
118                 this.textInterpolator = textInterpolator
119                 this.animator = valueAnimator
120             }
121 
122         textAnimator.setTextStyle(
123             weight = 400,
124             animate = true,
125             onAnimationEnd = animationEndCallback
126         )
127 
128         // Verify animationEnd callback has been added.
129         val captor = ArgumentCaptor.forClass(AnimatorListenerAdapter::class.java)
130         verify(valueAnimator).addListener(captor.capture())
131         captor.value.onAnimationEnd(valueAnimator)
132 
133         // Verify animationEnd callback has been invoked and removed.
134         verify(animationEndCallback).run()
135         verify(valueAnimator).removeListener(eq(captor.value))
136     }
137 
138     @Test
testCacheTypefacenull139     fun testCacheTypeface() {
140         val layout = makeLayout("Hello, World", PAINT)
141         val valueAnimator = mock(ValueAnimator::class.java)
142         val textInterpolator = mock(TextInterpolator::class.java)
143         val paint =
144             TextPaint().apply {
145                 typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf")
146             }
147         `when`(textInterpolator.targetPaint).thenReturn(paint)
148 
149         val textAnimator =
150             TextAnimator(layout, null, {}).apply {
151                 this.textInterpolator = textInterpolator
152                 this.animator = valueAnimator
153             }
154 
155         textAnimator.setTextStyle(weight = 400, animate = true)
156 
157         val prevTypeface = paint.typeface
158 
159         textAnimator.setTextStyle(weight = 700, animate = true)
160 
161         assertThat(paint.typeface).isNotSameInstanceAs(prevTypeface)
162 
163         textAnimator.setTextStyle(weight = 400, animate = true)
164 
165         assertThat(paint.typeface).isSameInstanceAs(prevTypeface)
166     }
167 }
168