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