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

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

Merge "Revert "Revert AllAppsRecyclerViewPoolTest.kt"" into main

parents a9095ca0 8fa1dcdc
Loading
Loading
Loading
Loading
+48 −12
Original line number Diff line number Diff line
@@ -18,18 +18,23 @@ package com.android.launcher3.recyclerview

import android.content.Context
import android.util.Log
import android.view.InflateException
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
import com.android.launcher3.BubbleTextView
import com.android.launcher3.BuildConfig
import com.android.launcher3.allapps.BaseAllAppsAdapter
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.util.CancellableTask
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
import com.android.launcher3.util.Themes
import com.android.launcher3.views.ActivityContext
import com.android.launcher3.views.ActivityContext.ActivityContextDelegate
import java.lang.IllegalStateException

const val PREINFLATE_ICONS_ROW_COUNT = 4
const val EXTRA_ICONS_COUNT = 2
@@ -39,10 +44,11 @@ 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

    companion object {
        private const val TAG = "AllAppsRecyclerViewPool"
@@ -53,7 +59,7 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
    /**
     * 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)
@@ -97,36 +103,65 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
                override fun getLayoutManager(): RecyclerView.LayoutManager? = null
            }

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

    @VisibleForTesting(otherwise = PROTECTED)
    fun preInflateAllAppsViewHolders(
        adapter: RecyclerView.Adapter<*>,
        viewType: Int,
        activeRv: RecyclerView,
        preInflationCount: Int,
        preInflationCountProvider: () -> Int,
    ) {
        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
                        }
                        // If activeRv's layout manager has been reset to null on main thread, skip
                        // the preinflation as we cannot generate correct LayoutParams
                        if (activeRv.layoutManager == null) {
                            list.clear()
                            break
                        }
                        try {
                            list.add(adapter.createViewHolder(activeRv, viewType))
                        } catch (e: InflateException) {
                            list.clear()
                            // It's still possible for UI thread to set activeRv's layout manager to
                            // null and we should break the loop and cancel the preinflation.
                            break
                        }
                        list.add(
                            adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON)
                        )
                    }
                    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)
    }

    /**
@@ -143,10 +178,11 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
     * app icons plus [EXTRA_ICONS_COUNT] is the magic minimal count of app icons to preinflate to
     * suffice fast scrolling.
     *
     * Note that we need to preinfate extra app icons in size of one all apps pages, so that opening
     * all apps don't need to inflate app icons.
     * Note that if [FeatureFlags.ALL_APPS_GONE_VISIBILITY] is enabled, we need to preinfate extra
     * 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
+120 −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.LayoutManager
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.Mockito.`when`
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: RecyclerView
    @Mock private lateinit var itemView: View
    @Mock private lateinit var layoutManager: LayoutManager

    @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)
        `when`(parent.layoutManager).thenReturn(layoutManager)
    }

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

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

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

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

    @Test
    fun preinflate_cancel_before_runOnMainThread() {
        underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 10) { 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) { 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
    }
}