/* * Copyright (C) 2020 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.animation import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.graphics.Typeface import android.text.Layout import android.text.StaticLayout import android.text.TextPaint import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import kotlin.math.ceil import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mockito.eq import org.mockito.Mockito.inOrder import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @RunWith(AndroidJUnit4::class) @SmallTest class TextAnimatorTest : SysuiTestCase() { private fun makeLayout(text: String, paint: TextPaint): Layout { val width = ceil(Layout.getDesiredWidth(text, 0, text.length, paint)).toInt() return StaticLayout.Builder.obtain(text, 0, text.length, paint, width).build() } @Test fun testAnimationStarted() { val layout = makeLayout("Hello, World", PAINT) val valueAnimator = mock(ValueAnimator::class.java) val textInterpolator = mock(TextInterpolator::class.java) val paint = mock(TextPaint::class.java) `when`(textInterpolator.targetPaint).thenReturn(paint) val textAnimator = TextAnimator(layout, null, {}).apply { this.textInterpolator = textInterpolator this.animator = valueAnimator } textAnimator.setTextStyle(weight = 400, animate = true) // If animation is requested, the base state should be rebased and the target state should // be updated. val order = inOrder(textInterpolator) order.verify(textInterpolator).rebase() order.verify(textInterpolator).onTargetPaintModified() // In case of animation, should not shape the base state since the animation should start // from current state. verify(textInterpolator, never()).onBasePaintModified() // Then, animation should be started. verify(valueAnimator, times(1)).start() } @Test fun testAnimationNotStarted() { val layout = makeLayout("Hello, World", PAINT) val valueAnimator = mock(ValueAnimator::class.java) val textInterpolator = mock(TextInterpolator::class.java) val paint = mock(TextPaint::class.java) `when`(textInterpolator.targetPaint).thenReturn(paint) val textAnimator = TextAnimator(layout, null, {}).apply { this.textInterpolator = textInterpolator this.animator = valueAnimator } textAnimator.setTextStyle(weight = 400, animate = false) // If animation is not requested, the progress should be 1 which is end of animation and the // base state is rebased to target state by calling rebase. val order = inOrder(textInterpolator) order.verify(textInterpolator).onTargetPaintModified() order.verify(textInterpolator).progress = 1f order.verify(textInterpolator).rebase() // Then, animation start should not be called. verify(valueAnimator, never()).start() } @Test fun testAnimationEnded() { val layout = makeLayout("Hello, World", PAINT) val valueAnimator = mock(ValueAnimator::class.java) val textInterpolator = mock(TextInterpolator::class.java) val paint = mock(TextPaint::class.java) `when`(textInterpolator.targetPaint).thenReturn(paint) val animationEndCallback = mock(Runnable::class.java) val textAnimator = TextAnimator(layout, null, {}).apply { this.textInterpolator = textInterpolator this.animator = valueAnimator } textAnimator.setTextStyle( weight = 400, animate = true, onAnimationEnd = animationEndCallback ) // Verify animationEnd callback has been added. val captor = ArgumentCaptor.forClass(AnimatorListenerAdapter::class.java) verify(valueAnimator).addListener(captor.capture()) captor.value.onAnimationEnd(valueAnimator) // Verify animationEnd callback has been invoked and removed. verify(animationEndCallback).run() verify(valueAnimator).removeListener(eq(captor.value)) } @Test fun testCacheTypeface() { val layout = makeLayout("Hello, World", PAINT) val valueAnimator = mock(ValueAnimator::class.java) val textInterpolator = mock(TextInterpolator::class.java) val paint = TextPaint().apply { typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf") } `when`(textInterpolator.targetPaint).thenReturn(paint) val textAnimator = TextAnimator(layout, null, {}).apply { this.textInterpolator = textInterpolator this.animator = valueAnimator } textAnimator.setTextStyle(weight = 400, animate = true) val prevTypeface = paint.typeface textAnimator.setTextStyle(weight = 700, animate = true) assertThat(paint.typeface).isNotSameInstanceAs(prevTypeface) textAnimator.setTextStyle(weight = 400, animate = true) assertThat(paint.typeface).isSameInstanceAs(prevTypeface) } }