1 /*
2  * Copyright 2024 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 @file:Suppress("DEPRECATION")
17 
18 package androidx.recyclerview.widget
19 
20 import android.graphics.Color
21 import android.os.Build
22 import android.view.View
23 import android.view.ViewGroup
24 import android.view.ViewTreeObserver
25 import android.widget.TextView
26 import androidx.test.ext.junit.runners.AndroidJUnit4
27 import androidx.test.filters.MediumTest
28 import androidx.test.filters.SdkSuppress
29 import androidx.test.rule.ActivityTestRule
30 import com.google.common.truth.Truth.assertThat
31 import java.util.concurrent.CountDownLatch
32 import java.util.concurrent.TimeUnit
33 import org.junit.Rule
34 import org.junit.Test
35 import org.junit.runner.RunWith
36 
37 @MediumTest
38 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
39 @RunWith(AndroidJUnit4::class)
40 class RecyclerViewScrollFrameRateTest {
41     @get:Rule val rule = ActivityTestRule(TestContentViewActivity::class.java)
42 
43     @Test
smoothScrollFrameRateBoostnull44     fun smoothScrollFrameRateBoost() {
45         val rv = RecyclerView(rule.activity)
46         rule.runOnUiThread {
47             rv.layoutManager =
48                 LinearLayoutManager(rule.activity, LinearLayoutManager.VERTICAL, false)
49             rv.adapter =
50                 object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
51                     override fun onCreateViewHolder(
52                         parent: ViewGroup,
53                         viewType: Int
54                     ): RecyclerView.ViewHolder {
55                         val view = TextView(parent.context)
56                         view.textSize = 40f
57                         view.setTextColor(Color.WHITE)
58                         return object : RecyclerView.ViewHolder(view) {}
59                     }
60 
61                     override fun getItemCount(): Int = 10000
62 
63                     override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
64                         val view = holder.itemView as TextView
65                         view.text = "Text $position"
66                         val color = if (position % 2 == 0) Color.BLACK else 0xFF000080.toInt()
67                         view.setBackgroundColor(color)
68                     }
69                 }
70             rule.activity.contentView.addView(rv)
71         }
72         runOnDraw(rv, { rv.smoothScrollBy(0, 1000) }) {
73             // First Frame
74             assertThat(rv.frameContentVelocity).isGreaterThan(0f)
75         }
76 
77         // Second frame
78         runOnDraw(rv) { assertThat(rv.frameContentVelocity).isGreaterThan(0f) }
79 
80         // Third frame
81         runOnDraw(rv) { assertThat(rv.frameContentVelocity).isGreaterThan(0f) }
82     }
83 
<lambda>null84     private fun runOnDraw(view: View, setup: () -> Unit = {}, onDraw: () -> Unit) {
85         val latch = CountDownLatch(1)
86         val onDrawListener =
<lambda>null87             ViewTreeObserver.OnDrawListener {
88                 latch.countDown()
89                 onDraw()
90             }
<lambda>null91         rule.runOnUiThread {
92             view.viewTreeObserver.addOnDrawListener(onDrawListener)
93             setup()
94         }
95         assertThat(latch.await(1, TimeUnit.SECONDS)).isTrue()
<lambda>null96         rule.runOnUiThread { view.viewTreeObserver.removeOnDrawListener(onDrawListener) }
97     }
98 }
99