• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.launcher3.recyclerview
18 
19 import android.content.Context
20 import androidx.recyclerview.widget.RecyclerView
21 import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
22 import androidx.recyclerview.widget.RecyclerView.ViewHolder
23 import com.android.launcher3.BubbleTextView
24 import com.android.launcher3.allapps.BaseAllAppsAdapter
25 import com.android.launcher3.config.FeatureFlags
26 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
27 import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
28 import com.android.launcher3.views.ActivityContext
29 import java.util.concurrent.Future
30 
31 const val PREINFLATE_ICONS_ROW_COUNT = 4
32 const val EXTRA_ICONS_COUNT = 2
33 
34 /**
35  * An [RecycledViewPool] that preinflates app icons ([ViewHolder] of [BubbleTextView]) of all apps
36  * [RecyclerView]. The view inflation will happen on background thread and inflated [ViewHolder]s
37  * will be added to [RecycledViewPool] on main thread.
38  */
39 class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
40 
41     private var future: Future<Void>? = null
42 
43     /**
44      * Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate.
45      */
preInflateAllAppsViewHoldersnull46     fun <T> preInflateAllAppsViewHolders(context: T) where T : Context, T : ActivityContext {
47         val appsView = context.appsView ?: return
48         val activeRv: RecyclerView = appsView.activeRecyclerView ?: return
49         val preInflateCount = getPreinflateCount(context)
50         if (preInflateCount <= 0) {
51             return
52         }
53 
54         // Because we perform onCreateViewHolder() on worker thread, we need a separate
55         // adapter/inflator object as they are not thread-safe. Note that the adapter
56         // just need to perform onCreateViewHolder(parent, VIEW_TYPE_ICON) so it doesn't need
57         // data source information.
58         val adapter: RecyclerView.Adapter<BaseAllAppsAdapter.ViewHolder> =
59             object : BaseAllAppsAdapter<T>(context, context.appsView.layoutInflater, null, null) {
60                 override fun setAppsPerRow(appsPerRow: Int) = Unit
61                 override fun getLayoutManager(): RecyclerView.LayoutManager? = null
62             }
63 
64         // Inflate view holders on background thread, and added to view pool on main thread.
65         future?.cancel(true)
66         future =
67             VIEW_PREINFLATION_EXECUTOR.submit<Void> {
68                 val viewHolders =
69                     Array(preInflateCount) {
70                         adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON)
71                     }
72                 MAIN_EXECUTOR.execute {
73                     for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) {
74                         putRecycledView(viewHolders[i])
75                     }
76                 }
77                 null
78             }
79     }
80 
81     /**
82      * After testing on phone, foldable and tablet, we found [PREINFLATE_ICONS_ROW_COUNT] rows of
83      * app icons plus [EXTRA_ICONS_COUNT] is the magic minimal count of app icons to preinflate to
84      * suffice fast scrolling.
85      *
86      * Note that if [FeatureFlags.ALL_APPS_GONE_VISIBILITY] is enabled, we need to preinfate extra
87      * app icons in size of one all apps pages, so that opening all apps don't need to inflate app
88      * icons.
89      */
getPreinflateCountnull90     fun <T> getPreinflateCount(context: T): Int where T : Context, T : ActivityContext {
91         var targetPreinflateCount =
92             PREINFLATE_ICONS_ROW_COUNT * context.deviceProfile.numShownAllAppsColumns +
93                 EXTRA_ICONS_COUNT
94         if (FeatureFlags.ALL_APPS_GONE_VISIBILITY.get()) {
95             val grid = ActivityContext.lookupContext<T>(context).deviceProfile
96             val approxRows =
97                 Math.ceil((grid.availableHeightPx / grid.allAppsIconSizePx).toDouble()).toInt()
98             targetPreinflateCount += (approxRows + 1) * grid.numShownAllAppsColumns
99         }
100         val existingPreinflateCount = getRecycledViewCount(BaseAllAppsAdapter.VIEW_TYPE_ICON)
101         return targetPreinflateCount - existingPreinflateCount
102     }
103 }
104