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