Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit abc4a958 authored by Fengjiang Li's avatar Fengjiang Li Committed by Android (Google) Code Review
Browse files

Merge "[Test Week] Add AllAppsRecyclerViewPoolTest" into main

parents a74d9d74 30d02c20
Loading
Loading
Loading
Loading
+31 −14
Original line number Diff line number Diff line
@@ -17,6 +17,9 @@
package com.android.launcher3.recyclerview

import android.content.Context
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.PROTECTED
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
import androidx.recyclerview.widget.RecyclerView.ViewHolder
@@ -38,21 +41,18 @@ const val EXTRA_ICONS_COUNT = 2
 * [RecyclerView]. The view inflation will happen on background thread and inflated [ViewHolder]s
 * will be added to [RecycledViewPool] on main thread.
 */
class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
class AllAppsRecyclerViewPool<T> : RecycledViewPool() where T : Context, T : ActivityContext {

    var hasWorkProfile = false
    private var mCancellableTask: CancellableTask<List<ViewHolder>>? = null
    @VisibleForTesting(otherwise = PROTECTED)
    var mCancellableTask: CancellableTask<List<ViewHolder>>? = null

    /**
     * Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate.
     */
    fun <T> preInflateAllAppsViewHolders(context: T) where T : Context, T : ActivityContext {
    fun preInflateAllAppsViewHolders(context: T) {
        val appsView = context.appsView ?: return
        val activeRv: RecyclerView = appsView.activeRecyclerView ?: return
        val preInflateCount = getPreinflateCount(context)
        if (preInflateCount <= 0) {
            return
        }

        // Create a separate context dedicated for all apps preinflation thread. The goal is to
        // create a separate AssetManager obj internally to avoid lock contention with
@@ -77,34 +77,51 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
                    null
                ) {
                override fun setAppsPerRow(appsPerRow: Int) = Unit

                override fun getLayoutManager(): RecyclerView.LayoutManager? = null
            }

        preInflateAllAppsViewHolders(adapter, BaseAllAppsAdapter.VIEW_TYPE_ICON, activeRv) {
            getPreinflateCount(context)
        }
    }

    @VisibleForTesting(otherwise = PROTECTED)
    fun preInflateAllAppsViewHolders(
        adapter: RecyclerView.Adapter<*>,
        viewType: Int,
        parent: ViewGroup,
        preInflationCountProvider: () -> Int
    ) {
        val preinflationCount = preInflationCountProvider.invoke()
        if (preinflationCount <= 0) {
            return
        }
        mCancellableTask?.cancel()
        var task: CancellableTask<List<ViewHolder>>? = null
        task =
            CancellableTask(
                {
                    val list: ArrayList<ViewHolder> = ArrayList()
                    for (i in 0 until preInflateCount) {
                    for (i in 0 until preinflationCount) {
                        if (task?.canceled == true) {
                            break
                        }
                        list.add(
                            adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON)
                        )
                        list.add(adapter.createViewHolder(parent, viewType))
                    }
                    list
                },
                MAIN_EXECUTOR,
                { viewHolders ->
                    for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) {
                    // Run preInflationCountProvider again as the needed VH might have changed
                    val newPreinflationCount = preInflationCountProvider.invoke()
                    for (i in 0 until minOf(viewHolders.size, newPreinflationCount)) {
                        putRecycledView(viewHolders[i])
                    }
                }
            )
        mCancellableTask = task
        VIEW_PREINFLATION_EXECUTOR.submit(mCancellableTask)
        VIEW_PREINFLATION_EXECUTOR.execute(mCancellableTask)
    }

    /**
@@ -125,7 +142,7 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
     * app icons in size of one all apps pages, so that opening all apps don't need to inflate app
     * icons.
     */
    fun <T> getPreinflateCount(context: T): Int where T : Context, T : ActivityContext {
    fun getPreinflateCount(context: T): Int {
        var targetPreinflateCount =
            PREINFLATE_ICONS_ROW_COUNT * context.deviceProfile.numShownAllAppsColumns +
                EXTRA_ICONS_COUNT
+116 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.launcher3.recyclerview

import android.content.Context
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.util.Executors
import com.android.launcher3.views.ActivityContext
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidJUnit4::class)
class AllAppsRecyclerViewPoolTest<T> where T : Context, T : ActivityContext {

    private lateinit var underTest: AllAppsRecyclerViewPool<T>
    private lateinit var adapter: RecyclerView.Adapter<*>

    @Mock private lateinit var parent: ViewGroup
    @Mock private lateinit var itemView: View

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        underTest = spy(AllAppsRecyclerViewPool())
        adapter =
            object : RecyclerView.Adapter<ViewHolder>() {
                override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
                    object : ViewHolder(itemView) {}

                override fun getItemCount() = 0

                override fun onBindViewHolder(holder: ViewHolder, position: Int) {}
            }
        underTest.setMaxRecycledViews(VIEW_TYPE, 20)
    }

    @Test
    fun preinflate_success() {
        underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }

        awaitTasksCompleted()
        assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(10)
    }

    @Test
    fun preinflate_not_triggered() {
        underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 0 }

        awaitTasksCompleted()
        assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
    }

    @Test
    fun preinflate_cancel_before_runOnMainThread() {
        underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
        assertThat(underTest.mCancellableTask!!.canceled).isFalse()

        underTest.clear()

        awaitTasksCompleted()
        verify(underTest, never()).putRecycledView(any(ViewHolder::class.java))
        assertThat(underTest.mCancellableTask!!.canceled).isTrue()
        assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
    }

    @Test
    fun preinflate_cancel_after_run() {
        underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
        assertThat(underTest.mCancellableTask!!.canceled).isFalse()
        awaitTasksCompleted()

        underTest.clear()

        verify(underTest, times(10)).putRecycledView(any(ViewHolder::class.java))
        assertThat(underTest.mCancellableTask!!.canceled).isTrue()
        assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
    }

    private fun awaitTasksCompleted() {
        Executors.VIEW_PREINFLATION_EXECUTOR.submit<Any> { null }.get()
        Executors.MAIN_EXECUTOR.submit<Any> { null }.get()
    }

    companion object {
        private const val VIEW_TYPE: Int = 4
    }
}