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 45 private val typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf") 46 makeLayoutnull47 private fun makeLayout(text: String, paint: TextPaint): Layout { 48 val width = ceil(Layout.getDesiredWidth(text, 0, text.length, paint)).toInt() 49 return StaticLayout.Builder.obtain(text, 0, text.length, paint, width).build() 50 } 51 52 @Test testAnimationStartednull53 fun testAnimationStarted() { 54 val layout = makeLayout("Hello, World", PAINT) 55 val valueAnimator = mock(ValueAnimator::class.java) 56 val textInterpolator = mock(TextInterpolator::class.java) 57 val paint = mock(TextPaint::class.java) 58 `when`(textInterpolator.targetPaint).thenReturn(paint) 59 60 val textAnimator = 61 TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply { 62 this.textInterpolator = textInterpolator 63 this.animator = valueAnimator 64 } 65 66 textAnimator.setTextStyle(weight = 400, animate = true) 67 68 // If animation is requested, the base state should be rebased and the target state should 69 // be updated. 70 val order = inOrder(textInterpolator) 71 order.verify(textInterpolator).rebase() 72 order.verify(textInterpolator).onTargetPaintModified() 73 74 // In case of animation, should not shape the base state since the animation should start 75 // from current state. 76 verify(textInterpolator, never()).onBasePaintModified() 77 78 // Then, animation should be started. 79 verify(valueAnimator, times(1)).start() 80 } 81 82 @Test testAnimationNotStartednull83 fun testAnimationNotStarted() { 84 val layout = makeLayout("Hello, World", PAINT) 85 val valueAnimator = mock(ValueAnimator::class.java) 86 val textInterpolator = mock(TextInterpolator::class.java) 87 val paint = mock(TextPaint::class.java) 88 `when`(textInterpolator.targetPaint).thenReturn(paint) 89 90 val textAnimator = 91 TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply { 92 this.textInterpolator = textInterpolator 93 this.animator = valueAnimator 94 } 95 96 textAnimator.setTextStyle(weight = 400, animate = false) 97 98 // If animation is not requested, the progress should be 1 which is end of animation and the 99 // base state is rebased to target state by calling rebase. 100 val order = inOrder(textInterpolator) 101 order.verify(textInterpolator).onTargetPaintModified() 102 order.verify(textInterpolator).progress = 1f 103 order.verify(textInterpolator).rebase() 104 105 // Then, animation start should not be called. 106 verify(valueAnimator, never()).start() 107 } 108 109 @Test testAnimationEndednull110 fun testAnimationEnded() { 111 val layout = makeLayout("Hello, World", PAINT) 112 val valueAnimator = mock(ValueAnimator::class.java) 113 val textInterpolator = mock(TextInterpolator::class.java) 114 val paint = mock(TextPaint::class.java) 115 `when`(textInterpolator.targetPaint).thenReturn(paint) 116 val animationEndCallback = mock(Runnable::class.java) 117 118 val textAnimator = 119 TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply { 120 this.textInterpolator = textInterpolator 121 this.animator = valueAnimator 122 } 123 124 textAnimator.setTextStyle( 125 weight = 400, 126 animate = true, 127 onAnimationEnd = animationEndCallback, 128 ) 129 130 // Verify animationEnd callback has been added. 131 val captor = ArgumentCaptor.forClass(AnimatorListenerAdapter::class.java) 132 verify(valueAnimator).addListener(captor.capture()) 133 captor.value.onAnimationEnd(valueAnimator) 134 135 // Verify animationEnd callback has been invoked and removed. 136 verify(animationEndCallback).run() 137 verify(valueAnimator).removeListener(eq(captor.value)) 138 } 139 140 @Test testCacheTypefacenull141 fun testCacheTypeface() { 142 val layout = makeLayout("Hello, World", PAINT) 143 val valueAnimator = mock(ValueAnimator::class.java) 144 val textInterpolator = mock(TextInterpolator::class.java) 145 val paint = TextPaint().apply { this.typeface = typeface } 146 `when`(textInterpolator.targetPaint).thenReturn(paint) 147 148 val textAnimator = 149 TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply { 150 this.textInterpolator = textInterpolator 151 this.animator = valueAnimator 152 } 153 154 textAnimator.setTextStyle(weight = 400, animate = true) 155 156 val prevTypeface = paint.typeface 157 158 textAnimator.setTextStyle(weight = 700, animate = true) 159 160 assertThat(paint.typeface).isNotSameInstanceAs(prevTypeface) 161 162 textAnimator.setTextStyle(weight = 400, animate = true) 163 164 assertThat(paint.typeface).isSameInstanceAs(prevTypeface) 165 } 166 } 167