• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.Context
21 import android.graphics.Bitmap
22 import android.graphics.drawable.BitmapDrawable
23 import android.graphics.drawable.Drawable
24 import android.os.UserHandle
25 import androidx.collection.LruCache
26 import com.android.intentresolver.Flags.targetHoverAndKeyboardFocusStates
27 import com.android.intentresolver.chooser.DisplayResolveInfo
28 import com.android.intentresolver.chooser.SelectableTargetInfo
29 import java.util.function.Consumer
30 import javax.annotation.concurrent.GuardedBy
31 import javax.inject.Qualifier
32 
33 @Qualifier @MustBeDocumented @Retention(AnnotationRetention.BINARY) annotation class Caching
34 
35 private typealias IconCache = LruCache<String, Bitmap>
36 
37 class CachingTargetDataLoader(
38     private val context: Context,
39     private val targetDataLoader: TargetDataLoader,
40     private val cacheSize: Int = 100,
41 ) : TargetDataLoader {
42     @GuardedBy("self") private val perProfileIconCache = HashMap<UserHandle, IconCache>()
43 
44     override fun getOrLoadAppTargetIcon(
45         info: DisplayResolveInfo,
46         userHandle: UserHandle,
47         callback: Consumer<Drawable>,
48     ): Drawable? {
49         val cacheKey = info.toCacheKey()
50         return getCachedAppIcon(cacheKey, userHandle)?.toDrawable()
51             ?: targetDataLoader.getOrLoadAppTargetIcon(info, userHandle) { drawable ->
52                 drawable.extractBitmap()?.let { getProfileIconCache(userHandle).put(cacheKey, it) }
53                 callback.accept(drawable)
54             }
55     }
56 
57     override fun getOrLoadDirectShareIcon(
58         info: SelectableTargetInfo,
59         userHandle: UserHandle,
60         callback: Consumer<Drawable>,
61     ): Drawable? {
62         val cacheKey = info.toCacheKey()
63         return cacheKey?.let { getCachedAppIcon(it, userHandle) }?.toDrawable()
64             ?: targetDataLoader.getOrLoadDirectShareIcon(info, userHandle) { drawable ->
65                 if (cacheKey != null) {
66                     drawable.extractBitmap()?.let {
67                         getProfileIconCache(userHandle).put(cacheKey, it)
68                     }
69                 }
70                 callback.accept(drawable)
71             }
72     }
73 
74     override fun loadLabel(info: DisplayResolveInfo, callback: Consumer<LabelInfo>) =
75         targetDataLoader.loadLabel(info, callback)
76 
77     override fun getOrLoadLabel(info: DisplayResolveInfo) = targetDataLoader.getOrLoadLabel(info)
78 
79     private fun getCachedAppIcon(component: String, userHandle: UserHandle): Bitmap? =
80         getProfileIconCache(userHandle)[component]
81 
82     private fun getProfileIconCache(userHandle: UserHandle): IconCache =
83         synchronized(perProfileIconCache) {
84             perProfileIconCache.getOrPut(userHandle) { IconCache(cacheSize) }
85         }
86 
87     private fun DisplayResolveInfo.toCacheKey() =
88         ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)
89             .flattenToString()
90 
91     private fun SelectableTargetInfo.toCacheKey(): String? =
92         if (chooserTargetIcon != null) {
93             // do not cache icons for caller-provided targets
94             null
95         } else {
96             buildString {
97                 append(chooserTargetComponentName?.flattenToString() ?: "")
98                 append("|")
99                 append(directShareShortcutInfo?.id ?: "")
100             }
101         }
102 
103     private fun Bitmap.toDrawable(): Drawable {
104         return if (targetHoverAndKeyboardFocusStates()) {
105             HoverBitmapDrawable(this)
106         } else {
107             BitmapDrawable(context.resources, this)
108         }
109     }
110 
111     private fun Drawable.extractBitmap(): Bitmap? {
112         return when (this) {
113             is BitmapDrawable -> bitmap
114             is HoverBitmapDrawable -> bitmap
115             else -> null
116         }
117     }
118 }
119