1 /* <lambda>null2 * 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.CancellableTask 27 import com.android.launcher3.util.Executors.MAIN_EXECUTOR 28 import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR 29 import com.android.launcher3.util.Themes 30 import com.android.launcher3.views.ActivityContext 31 import com.android.launcher3.views.ActivityContext.ActivityContextDelegate 32 33 const val PREINFLATE_ICONS_ROW_COUNT = 4 34 const val EXTRA_ICONS_COUNT = 2 35 36 /** 37 * An [RecycledViewPool] that preinflates app icons ([ViewHolder] of [BubbleTextView]) of all apps 38 * [RecyclerView]. The view inflation will happen on background thread and inflated [ViewHolder]s 39 * will be added to [RecycledViewPool] on main thread. 40 */ 41 class AllAppsRecyclerViewPool<T> : RecycledViewPool() { 42 43 var hasWorkProfile = false 44 private var mCancellableTask: CancellableTask<List<ViewHolder>>? = null 45 46 /** 47 * Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate. 48 */ 49 fun <T> preInflateAllAppsViewHolders(context: T) where T : Context, T : ActivityContext { 50 val appsView = context.appsView ?: return 51 val activeRv: RecyclerView = appsView.activeRecyclerView ?: return 52 val preInflateCount = getPreinflateCount(context) 53 if (preInflateCount <= 0) { 54 return 55 } 56 57 // Create a separate context dedicated for all apps preinflation thread. The goal is to 58 // create a separate AssetManager obj internally to avoid lock contention with 59 // AssetManager obj that is associated with the launcher context on the main thread. 60 val allAppsPreInflationContext = 61 ActivityContextDelegate( 62 context.createConfigurationContext(context.resources.configuration), 63 Themes.getActivityThemeRes(context), 64 context 65 ) 66 67 // Because we perform onCreateViewHolder() on worker thread, we need a separate 68 // adapter/inflator object as they are not thread-safe. Note that the adapter 69 // just need to perform onCreateViewHolder(parent, VIEW_TYPE_ICON) so it doesn't need 70 // data source information. 71 val adapter: RecyclerView.Adapter<BaseAllAppsAdapter.ViewHolder> = 72 object : 73 BaseAllAppsAdapter<T>( 74 context, 75 context.appsView.layoutInflater.cloneInContext(allAppsPreInflationContext), 76 null, 77 null 78 ) { 79 override fun setAppsPerRow(appsPerRow: Int) = Unit 80 override fun getLayoutManager(): RecyclerView.LayoutManager? = null 81 } 82 83 mCancellableTask?.cancel() 84 var task: CancellableTask<List<ViewHolder>>? = null 85 task = 86 CancellableTask( 87 { 88 val list: ArrayList<ViewHolder> = ArrayList() 89 for (i in 0 until preInflateCount) { 90 if (task?.canceled == true) { 91 break 92 } 93 list.add( 94 adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON) 95 ) 96 } 97 list 98 }, 99 MAIN_EXECUTOR, 100 { viewHolders -> 101 for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) { 102 putRecycledView(viewHolders[i]) 103 } 104 } 105 ) 106 mCancellableTask = task 107 VIEW_PREINFLATION_EXECUTOR.submit(mCancellableTask) 108 } 109 110 /** 111 * When clearing [RecycledViewPool], we should also abort pre-inflation tasks. This will make 112 * sure we don't inflate app icons after DeviceProfile has changed. 113 */ 114 override fun clear() { 115 super.clear() 116 mCancellableTask?.cancel() 117 } 118 119 /** 120 * After testing on phone, foldable and tablet, we found [PREINFLATE_ICONS_ROW_COUNT] rows of 121 * app icons plus [EXTRA_ICONS_COUNT] is the magic minimal count of app icons to preinflate to 122 * suffice fast scrolling. 123 * 124 * Note that if [FeatureFlags.ALL_APPS_GONE_VISIBILITY] is enabled, we need to preinfate extra 125 * app icons in size of one all apps pages, so that opening all apps don't need to inflate app 126 * icons. 127 */ 128 fun <T> getPreinflateCount(context: T): Int where T : Context, T : ActivityContext { 129 var targetPreinflateCount = 130 PREINFLATE_ICONS_ROW_COUNT * context.deviceProfile.numShownAllAppsColumns + 131 EXTRA_ICONS_COUNT 132 if (FeatureFlags.ALL_APPS_GONE_VISIBILITY.get()) { 133 val grid = ActivityContext.lookupContext<T>(context).deviceProfile 134 targetPreinflateCount += grid.maxAllAppsRowCount * grid.numShownAllAppsColumns 135 } 136 if (hasWorkProfile) { 137 targetPreinflateCount *= 2 138 } 139 val existingPreinflateCount = getRecycledViewCount(BaseAllAppsAdapter.VIEW_TYPE_ICON) 140 return targetPreinflateCount - existingPreinflateCount 141 } 142 } 143