diff options
| author | 2023-11-27 18:31:51 +0000 | |
|---|---|---|
| committer | 2023-11-27 18:31:51 +0000 | |
| commit | c1c24280a0dfe39745704398ce8d13cf14404eb2 (patch) | |
| tree | 1c03856622bdccb0bad98f91847ec34245972f59 /java/src/com | |
| parent | d43875a34e203f2ee25abfe7dba1f98d5a980d83 (diff) | |
| parent | b34ae5d8650a1a4f074db68cb64c3b9648cb9275 (diff) | |
Merge "Splits list controller into interfaces" into main
Diffstat (limited to 'java/src/com')
11 files changed, 585 insertions, 7 deletions
diff --git a/java/src/com/android/intentresolver/model/AbstractResolverComparator.java b/java/src/com/android/intentresolver/model/AbstractResolverComparator.java index 131aa8d9..724fa849 100644 --- a/java/src/com/android/intentresolver/model/AbstractResolverComparator.java +++ b/java/src/com/android/intentresolver/model/AbstractResolverComparator.java @@ -232,7 +232,7 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC * {@link ResolvedComponentInfo#getResolveInfoAt(int)} from the parameters of {@link * #compare(ResolvedComponentInfo, ResolvedComponentInfo)} */ - abstract int compare(ResolveInfo lhs, ResolveInfo rhs); + public abstract int compare(ResolveInfo lhs, ResolveInfo rhs); /** * Computes features for each target. This will be called before calls to {@link @@ -248,7 +248,7 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC } /** Implementation of compute called after {@link #beforeCompute()}. */ - abstract void doCompute(List<ResolvedComponentInfo> targets); + public abstract void doCompute(List<ResolvedComponentInfo> targets); /** * Returns the score that was calculated for the corresponding {@link ResolvedComponentInfo} @@ -257,12 +257,12 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC public abstract float getScore(TargetInfo targetInfo); /** Handles result message sent to mHandler. */ - abstract void handleResultMessage(Message message); + public abstract void handleResultMessage(Message message); /** * Reports to UsageStats what was chosen. */ - public final void updateChooserCounts(String packageName, UserHandle user, String action) { + public void updateChooserCounts(String packageName, UserHandle user, String action) { if (mUsmMap.containsKey(user)) { mUsmMap.get(user).reportChooserSelection( packageName, diff --git a/java/src/com/android/intentresolver/model/AppPredictionServiceResolverComparator.java b/java/src/com/android/intentresolver/model/AppPredictionServiceResolverComparator.java index 6f7b54dd..0651d26c 100644 --- a/java/src/com/android/intentresolver/model/AppPredictionServiceResolverComparator.java +++ b/java/src/com/android/intentresolver/model/AppPredictionServiceResolverComparator.java @@ -87,12 +87,12 @@ public class AppPredictionServiceResolverComparator extends AbstractResolverComp } @Override - int compare(ResolveInfo lhs, ResolveInfo rhs) { + public int compare(ResolveInfo lhs, ResolveInfo rhs) { return mComparatorModel.getComparator().compare(lhs, rhs); } @Override - void doCompute(List<ResolvedComponentInfo> targets) { + public void doCompute(List<ResolvedComponentInfo> targets) { if (targets.isEmpty()) { mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT); return; @@ -144,7 +144,7 @@ public class AppPredictionServiceResolverComparator extends AbstractResolverComp } @Override - void handleResultMessage(Message msg) { + public void handleResultMessage(Message msg) { // Null value is okay if we have defaulted to the ResolverRankerService. if (msg.what == RANKER_SERVICE_RESULT && msg.obj != null) { final List<AppTarget> sortedAppTargets = (List<AppTarget>) msg.obj; diff --git a/java/src/com/android/intentresolver/v2/listcontroller/FilterableComponents.kt b/java/src/com/android/intentresolver/v2/listcontroller/FilterableComponents.kt new file mode 100644 index 00000000..5855e2fc --- /dev/null +++ b/java/src/com/android/intentresolver/v2/listcontroller/FilterableComponents.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.v2.listcontroller + +import android.content.ComponentName +import com.android.intentresolver.ChooserRequestParameters + +/** A class that is able to identify components that should be hidden from the user. */ +interface FilterableComponents { + /** Whether this component should hidden from the user. */ + fun isComponentFiltered(name: ComponentName): Boolean +} + +/** A class that never filters components. */ +class NoComponentFiltering : FilterableComponents { + override fun isComponentFiltered(name: ComponentName): Boolean = false +} + +/** A class that filters components by chooser request filter. */ +class ChooserRequestFilteredComponents( + private val chooserRequestParameters: ChooserRequestParameters, +) : FilterableComponents { + override fun isComponentFiltered(name: ComponentName): Boolean = + chooserRequestParameters.filteredComponentNames.contains(name) +} diff --git a/java/src/com/android/intentresolver/v2/listcontroller/IntentResolver.kt b/java/src/com/android/intentresolver/v2/listcontroller/IntentResolver.kt new file mode 100644 index 00000000..bb9394b4 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/listcontroller/IntentResolver.kt @@ -0,0 +1,70 @@ +package com.android.intentresolver.v2.listcontroller + +import android.content.Intent +import android.content.pm.PackageManager +import android.os.UserHandle +import com.android.intentresolver.ResolvedComponentInfo + +/** A class for translating [Intent]s to [ResolvedComponentInfo]s. */ +interface IntentResolver { + /** + * Get data about all the ways the user with the specified handle can resolve any of the + * provided `intents`. + */ + fun getResolversForIntentAsUser( + shouldGetResolvedFilter: Boolean, + shouldGetActivityMetadata: Boolean, + shouldGetOnlyDefaultActivities: Boolean, + intents: List<Intent>, + userHandle: UserHandle, + ): List<ResolvedComponentInfo> +} + +/** Resolves [Intent]s using the [packageManager], deduping using the given [ResolveListDeduper]. */ +class IntentResolverImpl( + private val packageManager: PackageManager, + resolveListDeduper: ResolveListDeduper, +) : IntentResolver, ResolveListDeduper by resolveListDeduper { + override fun getResolversForIntentAsUser( + shouldGetResolvedFilter: Boolean, + shouldGetActivityMetadata: Boolean, + shouldGetOnlyDefaultActivities: Boolean, + intents: List<Intent>, + userHandle: UserHandle, + ): List<ResolvedComponentInfo> { + val baseFlags = + ((if (shouldGetOnlyDefaultActivities) PackageManager.MATCH_DEFAULT_ONLY else 0) or + PackageManager.MATCH_DIRECT_BOOT_AWARE or + PackageManager.MATCH_DIRECT_BOOT_UNAWARE or + (if (shouldGetResolvedFilter) PackageManager.GET_RESOLVED_FILTER else 0) or + (if (shouldGetActivityMetadata) PackageManager.GET_META_DATA else 0) or + PackageManager.MATCH_CLONE_PROFILE) + return getResolversForIntentAsUserInternal( + intents, + userHandle, + baseFlags, + ) + } + + private fun getResolversForIntentAsUserInternal( + intents: List<Intent>, + userHandle: UserHandle, + baseFlags: Int, + ): List<ResolvedComponentInfo> = buildList { + for (intent in intents) { + var flags = baseFlags + if (intent.isWebIntent || intent.flags and Intent.FLAG_ACTIVITY_MATCH_EXTERNAL != 0) { + flags = flags or PackageManager.MATCH_INSTANT + } + // Because of AIDL bug, queryIntentActivitiesAsUser can't accept subclasses of Intent. + val fixedIntent = + if (intent.javaClass != Intent::class.java) { + Intent(intent) + } else { + intent + } + val infos = packageManager.queryIntentActivitiesAsUser(fixedIntent, flags, userHandle) + addToResolveListWithDedupe(this, fixedIntent, infos) + } + } +} diff --git a/java/src/com/android/intentresolver/v2/listcontroller/LastChosenManager.kt b/java/src/com/android/intentresolver/v2/listcontroller/LastChosenManager.kt new file mode 100644 index 00000000..b2856526 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/listcontroller/LastChosenManager.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.v2.listcontroller + +import android.app.AppGlobals +import android.content.ContentResolver +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.IPackageManager +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.os.RemoteException +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +/** Class that stores and retrieves the most recently chosen resolutions. */ +interface LastChosenManager { + + /** Returns the most recently chosen resolution. */ + suspend fun getLastChosen(): ResolveInfo + + /** Sets the most recently chosen resolution. */ + suspend fun setLastChosen(intent: Intent, filter: IntentFilter, match: Int) +} + +/** + * Stores and retrieves the most recently chosen resolutions using the [PackageManager] provided by + * the [packageManagerProvider]. + */ +class PackageManagerLastChosenManager( + private val contentResolver: ContentResolver, + private val bgDispatcher: CoroutineDispatcher, + private val targetIntent: Intent, + private val packageManagerProvider: () -> IPackageManager = AppGlobals::getPackageManager, +) : LastChosenManager { + + @Throws(RemoteException::class) + override suspend fun getLastChosen(): ResolveInfo { + return withContext(bgDispatcher) { + packageManagerProvider() + .getLastChosenActivity( + targetIntent, + targetIntent.resolveTypeIfNeeded(contentResolver), + PackageManager.MATCH_DEFAULT_ONLY, + ) + } + } + + @Throws(RemoteException::class) + override suspend fun setLastChosen(intent: Intent, filter: IntentFilter, match: Int) { + return withContext(bgDispatcher) { + packageManagerProvider() + .setLastChosenActivity( + intent, + intent.resolveType(contentResolver), + PackageManager.MATCH_DEFAULT_ONLY, + filter, + match, + intent.component, + ) + } + } +} diff --git a/java/src/com/android/intentresolver/v2/listcontroller/ListController.kt b/java/src/com/android/intentresolver/v2/listcontroller/ListController.kt new file mode 100644 index 00000000..4ddab755 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/listcontroller/ListController.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.v2.listcontroller + +/** Controller for managing lists of [com.android.intentresolver.ResolvedComponentInfo]s. */ +interface ListController : + LastChosenManager, IntentResolver, ResolvedComponentFiltering, ResolvedComponentSorting diff --git a/java/src/com/android/intentresolver/v2/listcontroller/PermissionChecker.kt b/java/src/com/android/intentresolver/v2/listcontroller/PermissionChecker.kt new file mode 100644 index 00000000..cae2af95 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/listcontroller/PermissionChecker.kt @@ -0,0 +1,34 @@ +package com.android.intentresolver.v2.listcontroller + +import android.app.ActivityManager +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +/** Class for checking if a permission has been granted. */ +interface PermissionChecker { + /** Checks if the given [permission] has been granted. */ + suspend fun checkComponentPermission( + permission: String, + uid: Int, + owningUid: Int, + exported: Boolean, + ): Int +} + +/** + * Class for checking if a permission has been granted using the static + * [ActivityManager.checkComponentPermission]. + */ +class ActivityManagerPermissionChecker( + private val bgDispatcher: CoroutineDispatcher, +) : PermissionChecker { + override suspend fun checkComponentPermission( + permission: String, + uid: Int, + owningUid: Int, + exported: Boolean, + ): Int = + withContext(bgDispatcher) { + ActivityManager.checkComponentPermission(permission, uid, owningUid, exported) + } +} diff --git a/java/src/com/android/intentresolver/v2/listcontroller/PinnableComponents.kt b/java/src/com/android/intentresolver/v2/listcontroller/PinnableComponents.kt new file mode 100644 index 00000000..8be45ba2 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/listcontroller/PinnableComponents.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.v2.listcontroller + +import android.content.ComponentName +import android.content.SharedPreferences + +/** A class that is able to identify components that should be pinned for the user. */ +interface PinnableComponents { + /** Whether this component is pinned by the user. */ + fun isComponentPinned(name: ComponentName): Boolean +} + +/** A class that never pins components. */ +class NoComponentPinning : PinnableComponents { + override fun isComponentPinned(name: ComponentName): Boolean = false +} + +/** A class that determines pinnable components by user preferences. */ +class SharedPreferencesPinnedComponents( + private val pinnedSharedPreferences: SharedPreferences, +) : PinnableComponents { + override fun isComponentPinned(name: ComponentName): Boolean = + pinnedSharedPreferences.getBoolean(name.flattenToString(), false) +} diff --git a/java/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduper.kt b/java/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduper.kt new file mode 100644 index 00000000..f0b4bf3f --- /dev/null +++ b/java/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduper.kt @@ -0,0 +1,69 @@ +package com.android.intentresolver.v2.listcontroller + +import android.content.ComponentName +import android.content.Intent +import android.content.pm.ResolveInfo +import android.util.Log +import com.android.intentresolver.ResolvedComponentInfo + +/** A class for adding [ResolveInfo]s to a list of [ResolvedComponentInfo]s without duplicates. */ +interface ResolveListDeduper { + /** + * Adds [ResolveInfo]s in [from] to [ResolvedComponentInfo]s in [into], creating new + * [ResolvedComponentInfo]s when there is not already a corresponding one. + * + * This method may be destructive to both the given [into] list and the underlying + * [ResolvedComponentInfo]s. + */ + fun addToResolveListWithDedupe( + into: MutableList<ResolvedComponentInfo>, + intent: Intent, + from: List<ResolveInfo>, + ) +} + +/** + * Default implementation for adding [ResolveInfo]s to a list of [ResolvedComponentInfo]s without + * duplicates. Uses the given [PinnableComponents] to determine the pinning state of newly created + * [ResolvedComponentInfo]s. + */ +class ResolveListDeduperImpl(pinnableComponents: PinnableComponents) : + ResolveListDeduper, PinnableComponents by pinnableComponents { + override fun addToResolveListWithDedupe( + into: MutableList<ResolvedComponentInfo>, + intent: Intent, + from: List<ResolveInfo>, + ) { + from.forEach { newInfo -> + if (newInfo.userHandle == null) { + Log.w(TAG, "Skipping ResolveInfo with no userHandle: $newInfo") + return@forEach + } + val oldInfo = into.firstOrNull { isSameResolvedComponent(newInfo, it) } + // If existing resolution found, add to existing and filter out + if (oldInfo != null) { + oldInfo.add(intent, newInfo) + } else { + with(newInfo.activityInfo) { + into.add( + ResolvedComponentInfo( + ComponentName(packageName, name), + intent, + newInfo, + ) + .apply { isPinned = isComponentPinned(name) }, + ) + } + } + } + } + + private fun isSameResolvedComponent(a: ResolveInfo, b: ResolvedComponentInfo): Boolean { + val ai = a.activityInfo + return ai.packageName == b.name.packageName && ai.name == b.name.className + } + + companion object { + const val TAG = "ResolveListDeduper" + } +} diff --git a/java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFiltering.kt b/java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFiltering.kt new file mode 100644 index 00000000..e78bff00 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFiltering.kt @@ -0,0 +1,121 @@ +package com.android.intentresolver.v2.listcontroller + +import android.content.pm.PackageManager +import android.util.Log +import com.android.intentresolver.ResolvedComponentInfo +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope + +/** Provides filtering methods for lists of [ResolvedComponentInfo]. */ +interface ResolvedComponentFiltering { + /** + * Returns a list with all the [ResolvedComponentInfo] in [inputList], less the ones that are + * not eligible. + */ + suspend fun filterIneligibleActivities( + inputList: List<ResolvedComponentInfo>, + ): List<ResolvedComponentInfo> + + /** Filter out any low priority items. */ + fun filterLowPriority(inputList: List<ResolvedComponentInfo>): List<ResolvedComponentInfo> +} + +/** + * Default instantiation of the filtering methods for lists of [ResolvedComponentInfo]. + * + * Binder calls are performed on the given [bgDispatcher] and permissions are checked as if launched + * from the given [launchedFromUid] UID. Component filtering is handled by the given + * [FilterableComponents] and permission checking is handled by the given [PermissionChecker]. + */ +class ResolvedComponentFilteringImpl( + private val launchedFromUid: Int, + filterableComponents: FilterableComponents, + permissionChecker: PermissionChecker, +) : + ResolvedComponentFiltering, + PermissionChecker by permissionChecker, + FilterableComponents by filterableComponents { + constructor( + bgDispatcher: CoroutineDispatcher, + launchedFromUid: Int, + filterableComponents: FilterableComponents, + ) : this( + launchedFromUid = launchedFromUid, + filterableComponents = filterableComponents, + permissionChecker = ActivityManagerPermissionChecker(bgDispatcher), + ) + + /** + * Filter out items that are filtered by [FilterableComponents] or do not have the necessary + * permissions. + */ + override suspend fun filterIneligibleActivities( + inputList: List<ResolvedComponentInfo>, + ): List<ResolvedComponentInfo> = coroutineScope { + inputList + .map { + val activityInfo = it.getResolveInfoAt(0).activityInfo + if (isComponentFiltered(activityInfo.componentName)) { + CompletableDeferred(value = null) + } else { + // Do all permission checks in parallel + async { + val granted = + checkComponentPermission( + activityInfo.permission, + launchedFromUid, + activityInfo.applicationInfo.uid, + activityInfo.exported, + ) == PackageManager.PERMISSION_GRANTED + if (granted) it else null + } + } + } + .awaitAll() + .filterNotNull() + } + + /** + * Filters out all elements starting with the first elements with a different priority or + * default status than the first element. + */ + override fun filterLowPriority( + inputList: List<ResolvedComponentInfo>, + ): List<ResolvedComponentInfo> { + val firstResolveInfo = inputList[0].getResolveInfoAt(0) + // Only display the first matches that are either of equal + // priority or have asked to be default options. + val firstDiffIndex = + inputList.indexOfFirst { resolvedComponentInfo -> + val resolveInfo = resolvedComponentInfo.getResolveInfoAt(0) + if (firstResolveInfo == resolveInfo) { + false + } else { + if (DEBUG) { + Log.v( + TAG, + "${firstResolveInfo?.activityInfo?.name}=" + + "${firstResolveInfo?.priority}/${firstResolveInfo?.isDefault}" + + " vs ${resolveInfo?.activityInfo?.name}=" + + "${resolveInfo?.priority}/${resolveInfo?.isDefault}" + ) + } + firstResolveInfo!!.priority != resolveInfo!!.priority || + firstResolveInfo.isDefault != resolveInfo.isDefault + } + } + return if (firstDiffIndex == -1) { + inputList + } else { + inputList.subList(0, firstDiffIndex) + } + } + + companion object { + private const val TAG = "ResolvedComponentFilter" + private const val DEBUG = false + } +} diff --git a/java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSorting.kt b/java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSorting.kt new file mode 100644 index 00000000..8ab41ef0 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSorting.kt @@ -0,0 +1,108 @@ +package com.android.intentresolver.v2.listcontroller + +import android.os.UserHandle +import android.util.Log +import com.android.intentresolver.ResolvedComponentInfo +import com.android.intentresolver.chooser.DisplayResolveInfo +import com.android.intentresolver.chooser.TargetInfo +import com.android.intentresolver.model.AbstractResolverComparator +import java.util.concurrent.atomic.AtomicReference +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +/** Provides sorting methods for lists of [ResolvedComponentInfo]. */ +interface ResolvedComponentSorting { + /** Returns the a copy of the [inputList] sorted by app share score. */ + suspend fun sorted(inputList: List<ResolvedComponentInfo>?): List<ResolvedComponentInfo>? + + /** Returns the app share score of the [target]. */ + fun getScore(target: DisplayResolveInfo): Float + + /** Returns the app share score of the [targetInfo]. */ + fun getScore(targetInfo: TargetInfo): Float + + /** Updates the model about [targetInfo]. */ + suspend fun updateModel(targetInfo: TargetInfo) + + /** Updates the model about Activity selection. */ + suspend fun updateChooserCounts(packageName: String, user: UserHandle, action: String) + + /** Cleans up resources. Nothing should be called after calling this. */ + fun destroy() +} + +/** + * Provides sorting methods using the given [resolverComparator]. + * + * Long calculations and binder calls are performed on the given [bgDispatcher]. + */ +class ResolvedComponentSortingImpl( + private val bgDispatcher: CoroutineDispatcher, + private val resolverComparator: AbstractResolverComparator, +) : ResolvedComponentSorting { + + private val computeComplete = AtomicReference<CompletableDeferred<Unit>?>(null) + + @Throws(InterruptedException::class) + private suspend fun computeIfNeeded(inputList: List<ResolvedComponentInfo>) { + if (computeComplete.compareAndSet(null, CompletableDeferred())) { + resolverComparator.setCallBack { computeComplete.get()!!.complete(Unit) } + resolverComparator.compute(inputList) + } + with(computeComplete.get()!!) { if (isCompleted) return else return await() } + } + + override suspend fun sorted( + inputList: List<ResolvedComponentInfo>?, + ): List<ResolvedComponentInfo>? { + if (inputList.isNullOrEmpty()) return inputList + + return withContext(bgDispatcher) { + try { + val beforeRank = System.currentTimeMillis() + computeIfNeeded(inputList) + val sorted = inputList.sortedWith(resolverComparator) + val afterRank = System.currentTimeMillis() + if (DEBUG) { + Log.d(TAG, "Time Cost: ${afterRank - beforeRank}") + } + sorted + } catch (e: InterruptedException) { + Log.e(TAG, "Compute & Sort was interrupted: $e") + null + } + } + } + + override fun getScore(target: DisplayResolveInfo): Float { + return resolverComparator.getScore(target) + } + + override fun getScore(targetInfo: TargetInfo): Float { + return resolverComparator.getScore(targetInfo) + } + + override suspend fun updateModel(targetInfo: TargetInfo) { + withContext(bgDispatcher) { resolverComparator.updateModel(targetInfo) } + } + + override suspend fun updateChooserCounts( + packageName: String, + user: UserHandle, + action: String, + ) { + withContext(bgDispatcher) { + resolverComparator.updateChooserCounts(packageName, user, action) + } + } + + override fun destroy() { + resolverComparator.destroy() + } + + companion object { + private const val TAG = "ResolvedComponentSort" + private const val DEBUG = false + } +} |