1 /*
<lambda>null2  * Copyright 2024 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.intentresolver.icons
18 
19 import android.content.ComponentName
20 import android.graphics.drawable.Drawable
21 import android.os.UserHandle
22 import androidx.collection.LruCache
23 import com.android.intentresolver.chooser.DisplayResolveInfo
24 import com.android.intentresolver.chooser.SelectableTargetInfo
25 import java.util.function.Consumer
26 import javax.annotation.concurrent.GuardedBy
27 import javax.inject.Qualifier
28 
29 @Qualifier @MustBeDocumented @Retention(AnnotationRetention.BINARY) annotation class Caching
30 
31 private typealias IconCache = LruCache<String, Drawable>
32 
33 class CachingTargetDataLoader(
34     private val targetDataLoader: TargetDataLoader,
35     private val cacheSize: Int = 100,
36 ) : TargetDataLoader() {
37     @GuardedBy("self") private val perProfileIconCache = HashMap<UserHandle, IconCache>()
38 
39     override fun getOrLoadAppTargetIcon(
40         info: DisplayResolveInfo,
41         userHandle: UserHandle,
42         callback: Consumer<Drawable>
43     ): Drawable? {
44         val cacheKey = info.toCacheKey()
45         return getCachedAppIcon(cacheKey, userHandle)
46             ?: targetDataLoader.getOrLoadAppTargetIcon(info, userHandle) { drawable ->
47                 getProfileIconCache(userHandle).put(cacheKey, drawable)
48                 callback.accept(drawable)
49             }
50     }
51 
52     override fun getOrLoadDirectShareIcon(
53         info: SelectableTargetInfo,
54         userHandle: UserHandle,
55         callback: Consumer<Drawable>
56     ): Drawable? {
57         val cacheKey = info.toCacheKey()
58         return cacheKey?.let { getCachedAppIcon(it, userHandle) }
59             ?: targetDataLoader.getOrLoadDirectShareIcon(info, userHandle) { drawable ->
60                 if (cacheKey != null) {
61                     getProfileIconCache(userHandle).put(cacheKey, drawable)
62                 }
63                 callback.accept(drawable)
64             }
65     }
66 
67     override fun loadLabel(info: DisplayResolveInfo, callback: Consumer<LabelInfo>) =
68         targetDataLoader.loadLabel(info, callback)
69 
70     override fun getOrLoadLabel(info: DisplayResolveInfo) = targetDataLoader.getOrLoadLabel(info)
71 
72     private fun getCachedAppIcon(component: String, userHandle: UserHandle): Drawable? =
73         getProfileIconCache(userHandle)[component]
74 
75     private fun getProfileIconCache(userHandle: UserHandle): IconCache =
76         synchronized(perProfileIconCache) {
77             perProfileIconCache.getOrPut(userHandle) { IconCache(cacheSize) }
78         }
79 
80     private fun DisplayResolveInfo.toCacheKey() =
81         ComponentName(
82                 resolveInfo.activityInfo.packageName,
83                 resolveInfo.activityInfo.name,
84             )
85             .flattenToString()
86 
87     private fun SelectableTargetInfo.toCacheKey(): String? =
88         if (chooserTargetIcon != null) {
89             // do not cache icons for caller-provided targets
90             null
91         } else {
92             buildString {
93                 append(chooserTargetComponentName?.flattenToString() ?: "")
94                 append("|")
95                 append(directShareShortcutInfo?.id ?: "")
96             }
97         }
98 }
99