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.intentresolver.icons 18 19 import android.app.ActivityManager 20 import android.content.Context 21 import android.graphics.drawable.Drawable 22 import android.os.AsyncTask 23 import android.os.UserHandle 24 import android.util.SparseArray 25 import androidx.annotation.GuardedBy 26 import androidx.lifecycle.DefaultLifecycleObserver 27 import androidx.lifecycle.Lifecycle 28 import androidx.lifecycle.LifecycleOwner 29 import com.android.intentresolver.TargetPresentationGetter 30 import com.android.intentresolver.chooser.DisplayResolveInfo 31 import com.android.intentresolver.chooser.SelectableTargetInfo 32 import java.util.concurrent.atomic.AtomicInteger 33 import java.util.function.Consumer 34 import kotlinx.coroutines.Dispatchers 35 import kotlinx.coroutines.asExecutor 36 37 /** An actual [TargetDataLoader] implementation. */ 38 // TODO: replace async tasks with coroutines. 39 class DefaultTargetDataLoader( 40 private val context: Context, 41 private val lifecycle: Lifecycle, 42 private val isAudioCaptureDevice: Boolean, 43 ) : TargetDataLoader() { 44 private val presentationFactory = 45 TargetPresentationGetter.Factory( 46 context, 47 context.getSystemService(ActivityManager::class.java)?.launcherLargeIconDensity 48 ?: error("Unable to access ActivityManager") 49 ) 50 private val nextTaskId = AtomicInteger(0) 51 @GuardedBy("self") private val activeTasks = SparseArray<AsyncTask<*, *, *>>() 52 private val executor = Dispatchers.IO.asExecutor() 53 54 init { 55 lifecycle.addObserver( 56 object : DefaultLifecycleObserver { 57 override fun onDestroy(owner: LifecycleOwner) { 58 lifecycle.removeObserver(this) 59 destroy() 60 } 61 } 62 ) 63 } 64 65 override fun getOrLoadAppTargetIcon( 66 info: DisplayResolveInfo, 67 userHandle: UserHandle, 68 callback: Consumer<Drawable>, 69 ): Drawable? { 70 val taskId = nextTaskId.getAndIncrement() 71 LoadIconTask(context, info, userHandle, presentationFactory) { result -> 72 removeTask(taskId) 73 callback.accept(result) 74 } 75 .also { addTask(taskId, it) } 76 .executeOnExecutor(executor) 77 return null 78 } 79 80 override fun getOrLoadDirectShareIcon( 81 info: SelectableTargetInfo, 82 userHandle: UserHandle, 83 callback: Consumer<Drawable>, 84 ): Drawable? { 85 val taskId = nextTaskId.getAndIncrement() 86 LoadDirectShareIconTask( 87 context.createContextAsUser(userHandle, 0), 88 info, 89 presentationFactory, 90 ) { result -> 91 removeTask(taskId) 92 callback.accept(result) 93 } 94 .also { addTask(taskId, it) } 95 .executeOnExecutor(executor) 96 return null 97 } 98 99 override fun loadLabel(info: DisplayResolveInfo, callback: Consumer<LabelInfo>) { 100 val taskId = nextTaskId.getAndIncrement() 101 LoadLabelTask(context, info, isAudioCaptureDevice, presentationFactory) { result -> 102 removeTask(taskId) 103 callback.accept(result) 104 } 105 .also { addTask(taskId, it) } 106 .executeOnExecutor(executor) 107 } 108 109 override fun getOrLoadLabel(info: DisplayResolveInfo) { 110 if (!info.hasDisplayLabel()) { 111 val result = 112 LoadLabelTask.loadLabel(context, info, isAudioCaptureDevice, presentationFactory) 113 info.displayLabel = result.label 114 info.extendedInfo = result.subLabel 115 } 116 } 117 118 private fun addTask(id: Int, task: AsyncTask<*, *, *>) { 119 synchronized(activeTasks) { activeTasks.put(id, task) } 120 } 121 122 private fun removeTask(id: Int) { 123 synchronized(activeTasks) { activeTasks.remove(id) } 124 } 125 126 private fun destroy() { 127 synchronized(activeTasks) { 128 for (i in 0 until activeTasks.size()) { 129 activeTasks.valueAt(i).cancel(false) 130 } 131 activeTasks.clear() 132 } 133 } 134 } 135