diff options
| author | 2022-04-01 15:17:50 -0400 | |
|---|---|---|
| committer | 2022-04-22 16:05:39 +0000 | |
| commit | 6cdd926f43c7cdbfe17a7f03a2f50f6973925af1 (patch) | |
| tree | 60224600363c476782da6ca2f9d80228700cc3a2 | |
| parent | 31cc9ddb593f5c0169b9d6e05002a2c28b4f91e2 (diff) | |
Create ResolverComparatorModel interface.
Define the API for interacting with comparator model data;
provide implementations for our two current model types; and
(as a first step) re-write our legacy ResolverComparators
to be implemented internally in terms of their new model types.
This is the first CL in a multi-part cleanup of the
AbstractResolverComparator design. This demonstrates that the
role of an AbstractResolverComparator sub-class amounts to
(i.e., Ctrl+F "@Override") some amount of work to prepare model
data; some cleanup; and a set query methods against data that
*really should be* immutable (separated in this CL as the new
ResolverComparatorModel interace). Any remaining responsibilities
of the abstract base class would be better handled (in a subsequent
CL) by an external controller operating on a ResolverComparatorModel
(i.e., preferring composition to inheritance). The async
model-preparation steps should also be separated and cleaned
up (in a later CL).
I believe this to be a pure refactoring with no observable side
effects. While the new design aids in implementing the correct
"immutable snapshot" style, for now I've written the new
ResolverComparatorModel implementations to preserve any possible
quirks in the legacy implementations. Nevertheless, if some
inadvertant behavior change is introduced as a result of this CL,
it's most likely to be a bug-fix where we previously would've mixed
in stale data. A later CL will intentionally pursue those fixes.
Test: atest ResolverActivityTest ChooserActivityTest
Bug: 227486788
Change-Id: If88bf7a5a6394d81c021782d5d9bce7955f1c0e6
Merged-In: If88bf7a5a6394d81c021782d5d9bce7955f1c0e6
(cherry picked from commit 8b5d279d9047487e151ea0be91bb1c19042fedd3)
8 files changed, 383 insertions, 154 deletions
diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java index 42fc7bd6e6fc..975954035c17 100644 --- a/core/java/com/android/internal/app/AbstractResolverComparator.java +++ b/core/java/com/android/internal/app/AbstractResolverComparator.java @@ -228,12 +228,6 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC */ abstract float getScore(ComponentName name); - /** - * Returns the list of top K component names which have highest - * {@link #getScore(ComponentName)} - */ - abstract List<ComponentName> getTopComponentNames(int topK); - /** Handles result message sent to mHandler. */ abstract void handleResultMessage(Message message); diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java index bc9eff04636d..b19ac2fec640 100644 --- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java +++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java @@ -18,6 +18,7 @@ package com.android.internal.app; import static android.app.prediction.AppTargetEvent.ACTION_LAUNCH; +import android.annotation.Nullable; import android.app.prediction.AppPredictor; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; @@ -33,12 +34,11 @@ import android.util.Log; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.Executors; -import java.util.stream.Collectors; /** * Uses an {@link AppPredictor} to sort Resolver targets. If the AppPredictionService appears to be @@ -58,7 +58,9 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator private final String mReferrerPackage; // If this is non-null (and this is not destroyed), it means APS is disabled and we should fall // back to using the ResolverRankerService. + // TODO: responsibility for this fallback behavior can live outside of the AppPrediction client. private ResolverRankerServiceResolverComparator mResolverRankerService; + private AppPredictionServiceComparatorModel mComparatorModel; AppPredictionServiceResolverComparator( Context context, @@ -74,25 +76,12 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator mUser = user; mReferrerPackage = referrerPackage; setChooserActivityLogger(chooserActivityLogger); + mComparatorModel = buildUpdatedModel(); } @Override int compare(ResolveInfo lhs, ResolveInfo rhs) { - if (mResolverRankerService != null) { - return mResolverRankerService.compare(lhs, rhs); - } - Integer lhsRank = mTargetRanks.get(new ComponentName(lhs.activityInfo.packageName, - lhs.activityInfo.name)); - Integer rhsRank = mTargetRanks.get(new ComponentName(rhs.activityInfo.packageName, - rhs.activityInfo.name)); - if (lhsRank == null && rhsRank == null) { - return 0; - } else if (lhsRank == null) { - return -1; - } else if (rhsRank == null) { - return 1; - } - return lhsRank - rhsRank; + return mComparatorModel.getComparator().compare(lhs, rhs); } @Override @@ -121,6 +110,7 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator mContext, mIntent, mReferrerPackage, () -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT), getChooserActivityLogger()); + mComparatorModel = buildUpdatedModel(); mResolverRankerService.compute(targets); } else { Log.i(TAG, "AppPredictionService response received"); @@ -163,6 +153,7 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator mTargetRanks.put(componentName, i); Log.i(TAG, "handleSortedAppTargets, sortedAppTargets #" + i + ": " + componentName); } + mComparatorModel = buildUpdatedModel(); } private boolean checkAppTargetRankValid(List<AppTarget> sortedAppTargets) { @@ -176,43 +167,12 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator @Override float getScore(ComponentName name) { - if (mResolverRankerService != null) { - return mResolverRankerService.getScore(name); - } - Integer rank = mTargetRanks.get(name); - if (rank == null) { - Log.w(TAG, "Score requested for unknown component. Did you call compute yet?"); - return 0f; - } - int consecutiveSumOfRanks = (mTargetRanks.size() - 1) * (mTargetRanks.size()) / 2; - return 1.0f - (((float) rank) / consecutiveSumOfRanks); - } - - @Override - List<ComponentName> getTopComponentNames(int topK) { - if (mResolverRankerService != null) { - return mResolverRankerService.getTopComponentNames(topK); - } - return mTargetRanks.entrySet().stream() - .sorted(Entry.comparingByValue()) - .limit(topK) - .map(Entry::getKey) - .collect(Collectors.toList()); + return mComparatorModel.getScore(name); } @Override void updateModel(ComponentName componentName) { - if (mResolverRankerService != null) { - mResolverRankerService.updateModel(componentName); - return; - } - mAppPredictor.notifyAppTargetEvent( - new AppTargetEvent.Builder( - new AppTarget.Builder( - new AppTargetId(componentName.toString()), - componentName.getPackageName(), mUser) - .setClassName(componentName.getClassName()).build(), - ACTION_LAUNCH).build()); + mComparatorModel.notifyOnTargetSelected(componentName); } @Override @@ -220,6 +180,97 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator if (mResolverRankerService != null) { mResolverRankerService.destroy(); mResolverRankerService = null; + mComparatorModel = buildUpdatedModel(); + } + } + + /** + * Re-construct an {@code AppPredictionServiceComparatorModel} to replace the current model + * instance (if any) using the up-to-date {@code AppPredictionServiceResolverComparator} ivar + * values. + * + * TODO: each time we replace the model instance, we're either updating the model to use + * adjusted data (which is appropriate), or we're providing a (late) value for one of our ivars + * that wasn't available the last time the model was updated. For those latter cases, we should + * just avoid creating the model altogether until we have all the prerequisites we'll need. Then + * we can probably simplify the logic in {@code AppPredictionServiceComparatorModel} since we + * won't need to handle edge cases when the model data isn't fully prepared. + * (In some cases, these kinds of "updates" might interleave -- e.g., we might have finished + * initializing the first time and now want to adjust some data, but still need to wait for + * changes to propagate to the other ivars before rebuilding the model.) + */ + private AppPredictionServiceComparatorModel buildUpdatedModel() { + return new AppPredictionServiceComparatorModel( + mAppPredictor, mResolverRankerService, mUser, mTargetRanks); + } + + // TODO: Finish separating behaviors of AbstractResolverComparator, then (probably) make this a + // standalone class once clients are written in terms of ResolverComparatorModel. + static class AppPredictionServiceComparatorModel implements ResolverComparatorModel { + private final AppPredictor mAppPredictor; + private final ResolverRankerServiceResolverComparator mResolverRankerService; + private final UserHandle mUser; + private final Map<ComponentName, Integer> mTargetRanks; // Treat as immutable. + + AppPredictionServiceComparatorModel( + AppPredictor appPredictor, + @Nullable ResolverRankerServiceResolverComparator resolverRankerService, + UserHandle user, + Map<ComponentName, Integer> targetRanks) { + mAppPredictor = appPredictor; + mResolverRankerService = resolverRankerService; + mUser = user; + mTargetRanks = targetRanks; + } + + @Override + public Comparator<ResolveInfo> getComparator() { + return (lhs, rhs) -> { + if (mResolverRankerService != null) { + return mResolverRankerService.compare(lhs, rhs); + } + Integer lhsRank = mTargetRanks.get(new ComponentName(lhs.activityInfo.packageName, + lhs.activityInfo.name)); + Integer rhsRank = mTargetRanks.get(new ComponentName(rhs.activityInfo.packageName, + rhs.activityInfo.name)); + if (lhsRank == null && rhsRank == null) { + return 0; + } else if (lhsRank == null) { + return -1; + } else if (rhsRank == null) { + return 1; + } + return lhsRank - rhsRank; + }; + } + + @Override + public float getScore(ComponentName name) { + if (mResolverRankerService != null) { + return mResolverRankerService.getScore(name); + } + Integer rank = mTargetRanks.get(name); + if (rank == null) { + Log.w(TAG, "Score requested for unknown component. Did you call compute yet?"); + return 0f; + } + int consecutiveSumOfRanks = (mTargetRanks.size() - 1) * (mTargetRanks.size()) / 2; + return 1.0f - (((float) rank) / consecutiveSumOfRanks); + } + + @Override + public void notifyOnTargetSelected(ComponentName componentName) { + if (mResolverRankerService != null) { + mResolverRankerService.updateModel(componentName); + return; + } + mAppPredictor.notifyAppTargetEvent( + new AppTargetEvent.Builder( + new AppTarget.Builder( + new AppTargetId(componentName.toString()), + componentName.getPackageName(), mUser) + .setClassName(componentName.getClassName()).build(), + ACTION_LAUNCH).build()); } } } diff --git a/core/java/com/android/internal/app/ResolverComparatorModel.java b/core/java/com/android/internal/app/ResolverComparatorModel.java new file mode 100644 index 000000000000..3e8f64bf4ed3 --- /dev/null +++ b/core/java/com/android/internal/app/ResolverComparatorModel.java @@ -0,0 +1,57 @@ +/* + * Copyright 2022 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.internal.app; + +import android.content.ComponentName; +import android.content.pm.ResolveInfo; + +import java.util.Comparator; +import java.util.List; + +/** + * A ranking model for resolver targets, providing ordering and (optionally) numerical scoring. + * + * As required by the {@link Comparator} contract, objects returned by {@code getComparator()} must + * apply a total ordering on its inputs consistent across all calls to {@code Comparator#compare()}. + * Other query methods and ranking feedback should refer to that same ordering, so implementors are + * generally advised to "lock in" an immutable snapshot of their model data when this object is + * initialized (preferring to replace the entire {@code ResolverComparatorModel} instance if the + * backing data needs to be updated in the future). + */ +interface ResolverComparatorModel { + /** + * Get a {@code Comparator} that can be used to sort {@code ResolveInfo} targets according to + * the model ranking. + */ + Comparator<ResolveInfo> getComparator(); + + /** + * Get the numerical score, if any, that the model assigns to the component with the specified + * {@code name}. Scores range from zero to one, with one representing the highest possible + * likelihood that the user will select that component as the target. Implementations that don't + * assign numerical scores are <em>recommended</em> to return a value of 0 for all components. + */ + float getScore(ComponentName name); + + /** + * Notify the model that the user selected a target. (Models may log this information, use it as + * a feedback signal for their ranking, etc.) Because the data in this + * {@code ResolverComparatorModel} instance is immutable, clients will need to get an up-to-date + * instance in order to see any changes in the ranking that might result from this feedback. + */ + void notifyOnTargetSelected(ComponentName componentName); +} diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index ac9b2d8a8d8f..988ecc78d408 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -155,14 +155,6 @@ public class ResolverListAdapter extends BaseAdapter { return mResolverListController.getScore(componentName); } - /** - * Returns the list of top K component names which have highest - * {@link #getScore(DisplayResolveInfo)} - */ - public List<ComponentName> getTopComponentNames(int topK) { - return mResolverListController.getTopComponentNames(topK); - } - public void updateModel(ComponentName componentName) { mResolverListController.updateModel(componentName); } diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index 9a95e6411fa4..27573631b2ce 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -393,14 +393,6 @@ public class ResolverListController { return mResolverComparator.getScore(componentName); } - /** - * Returns the list of top K component names which have highest - * {@link #getScore(DisplayResolveInfo)} - */ - public List<ComponentName> getTopComponentNames(int topK) { - return mResolverComparator.getTopComponentNames(topK); - } - public void updateModel(ComponentName componentName) { mResolverComparator.updateModel(componentName); } diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java index cb946c0dcf99..c5b21ac4da90 100644 --- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java +++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java @@ -43,12 +43,12 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.text.Collator; import java.util.ArrayList; +import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; /** * Ranks and compares packages based on usage stats and uses the {@link ResolverRankerService}. @@ -83,6 +83,7 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator private ResolverRankerServiceConnection mConnection; private Context mContext; private CountDownLatch mConnectSignal; + private ResolverRankerServiceComparatorModel mComparatorModel; public ResolverRankerServiceResolverComparator(Context context, Intent intent, String referrerPackage, AfterCompute afterCompute, @@ -99,6 +100,8 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator mRankerServiceName = new ComponentName(mContext, this.getClass()); setCallBack(afterCompute); setChooserActivityLogger(chooserActivityLogger); + + mComparatorModel = buildUpdatedModel(); } @Override @@ -125,6 +128,7 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator } if (isUpdated) { mRankerServiceName = mResolvedRankerName; + mComparatorModel = buildUpdatedModel(); } } else { Log.e(TAG, "Sizes of sent and received ResolverTargets diff."); @@ -218,83 +222,25 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator } } predictSelectProbabilities(mTargets); + + mComparatorModel = buildUpdatedModel(); } @Override public int compare(ResolveInfo lhs, ResolveInfo rhs) { - if (mStats != null) { - final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName( - lhs.activityInfo.packageName, lhs.activityInfo.name)); - final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName( - rhs.activityInfo.packageName, rhs.activityInfo.name)); - - if (lhsTarget != null && rhsTarget != null) { - final int selectProbabilityDiff = Float.compare( - rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability()); - - if (selectProbabilityDiff != 0) { - return selectProbabilityDiff > 0 ? 1 : -1; - } - } - } - - CharSequence sa = lhs.loadLabel(mPm); - if (sa == null) sa = lhs.activityInfo.name; - CharSequence sb = rhs.loadLabel(mPm); - if (sb == null) sb = rhs.activityInfo.name; - - return mCollator.compare(sa.toString().trim(), sb.toString().trim()); + return mComparatorModel.getComparator().compare(lhs, rhs); } @Override public float getScore(ComponentName name) { - final ResolverTarget target = mTargetsDict.get(name); - if (target != null) { - return target.getSelectProbability(); - } - return 0; - } - - @Override - List<ComponentName> getTopComponentNames(int topK) { - return mTargetsDict.entrySet().stream() - .sorted((o1, o2) -> -Float.compare(getScore(o1.getKey()), getScore(o2.getKey()))) - .limit(topK) - .map(Map.Entry::getKey) - .collect(Collectors.toList()); + return mComparatorModel.getScore(name); } // update ranking model when the connection to it is valid. @Override public void updateModel(ComponentName componentName) { synchronized (mLock) { - if (mRanker != null) { - try { - int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet()) - .indexOf(componentName); - if (selectedPos >= 0 && mTargets != null) { - final float selectedProbability = getScore(componentName); - int order = 0; - for (ResolverTarget target : mTargets) { - if (target.getSelectProbability() > selectedProbability) { - order++; - } - } - logMetrics(order); - mRanker.train(mTargets, selectedPos); - } else { - if (DEBUG) { - Log.d(TAG, "Selected a unknown component: " + componentName); - } - } - } catch (RemoteException e) { - Log.e(TAG, "Error in Train: " + e); - } - } else { - if (DEBUG) { - Log.d(TAG, "Ranker is null; skip updateModel."); - } - } + mComparatorModel.notifyOnTargetSelected(componentName); } } @@ -313,19 +259,6 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator } } - // records metrics for evaluation. - private void logMetrics(int selectedPos) { - if (mRankerServiceName != null) { - MetricsLogger metricsLogger = new MetricsLogger(); - LogMaker log = new LogMaker(MetricsEvent.ACTION_TARGET_SELECTED); - log.setComponentName(mRankerServiceName); - int isCategoryUsed = (mAnnotations == null) ? 0 : 1; - log.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, isCategoryUsed); - log.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION, selectedPos); - metricsLogger.write(log); - } - } - // connect to a ranking service. private void initRanker(Context context) { synchronized (mLock) { @@ -426,6 +359,7 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator } synchronized (mLock) { mRanker = IResolverRankerService.Stub.asInterface(service); + mComparatorModel = buildUpdatedModel(); mConnectSignal.countDown(); } } @@ -443,6 +377,7 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator public void destroy() { synchronized (mLock) { mRanker = null; + mComparatorModel = buildUpdatedModel(); } } } @@ -453,6 +388,7 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator mTargetsDict.clear(); mTargets = null; mRankerServiceName = new ComponentName(mContext, this.getClass()); + mComparatorModel = buildUpdatedModel(); mResolvedRankerName = null; initRanker(mContext); } @@ -508,4 +444,155 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator } return false; } + + /** + * Re-construct a {@code ResolverRankerServiceComparatorModel} to replace the current model + * instance (if any) using the up-to-date {@code ResolverRankerServiceResolverComparator} ivar + * values. + * + * TODO: each time we replace the model instance, we're either updating the model to use + * adjusted data (which is appropriate), or we're providing a (late) value for one of our ivars + * that wasn't available the last time the model was updated. For those latter cases, we should + * just avoid creating the model altogether until we have all the prerequisites we'll need. Then + * we can probably simplify the logic in {@code ResolverRankerServiceComparatorModel} since we + * won't need to handle edge cases when the model data isn't fully prepared. + * (In some cases, these kinds of "updates" might interleave -- e.g., we might have finished + * initializing the first time and now want to adjust some data, but still need to wait for + * changes to propagate to the other ivars before rebuilding the model.) + */ + private ResolverRankerServiceComparatorModel buildUpdatedModel() { + // TODO: we don't currently guarantee that the underlying target list/map won't be mutated, + // so the ResolverComparatorModel may provide inconsistent results. We should make immutable + // copies of the data (waiting for any necessary remaining data before creating the model). + return new ResolverRankerServiceComparatorModel( + mStats, + mTargetsDict, + mTargets, + mCollator, + mRanker, + mRankerServiceName, + (mAnnotations != null), + mPm); + } + + /** + * Implementation of a {@code ResolverComparatorModel} that provides the same ranking logic as + * the legacy {@code ResolverRankerServiceResolverComparator}, as a refactoring step toward + * removing the complex legacy API. + */ + static class ResolverRankerServiceComparatorModel implements ResolverComparatorModel { + private final Map<String, UsageStats> mStats; // Treat as immutable. + private final Map<ComponentName, ResolverTarget> mTargetsDict; // Treat as immutable. + private final List<ResolverTarget> mTargets; // Treat as immutable. + private final Collator mCollator; + private final IResolverRankerService mRanker; + private final ComponentName mRankerServiceName; + private final boolean mAnnotationsUsed; + private final PackageManager mPm; + + // TODO: it doesn't look like we should have to pass both targets and targetsDict, but it's + // not written in a way that makes it clear whether we can derive one from the other (at + // least in this constructor). + ResolverRankerServiceComparatorModel( + Map<String, UsageStats> stats, + Map<ComponentName, ResolverTarget> targetsDict, + List<ResolverTarget> targets, + Collator collator, + IResolverRankerService ranker, + ComponentName rankerServiceName, + boolean annotationsUsed, + PackageManager pm) { + mStats = stats; + mTargetsDict = targetsDict; + mTargets = targets; + mCollator = collator; + mRanker = ranker; + mRankerServiceName = rankerServiceName; + mAnnotationsUsed = annotationsUsed; + mPm = pm; + } + + @Override + public Comparator<ResolveInfo> getComparator() { + // TODO: doCompute() doesn't seem to be concerned about null-checking mStats. Is that + // a bug there, or do we have a way of knowing it will be non-null under certain + // conditions? + return (lhs, rhs) -> { + if (mStats != null) { + final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName( + lhs.activityInfo.packageName, lhs.activityInfo.name)); + final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName( + rhs.activityInfo.packageName, rhs.activityInfo.name)); + + if (lhsTarget != null && rhsTarget != null) { + final int selectProbabilityDiff = Float.compare( + rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability()); + + if (selectProbabilityDiff != 0) { + return selectProbabilityDiff > 0 ? 1 : -1; + } + } + } + + CharSequence sa = lhs.loadLabel(mPm); + if (sa == null) sa = lhs.activityInfo.name; + CharSequence sb = rhs.loadLabel(mPm); + if (sb == null) sb = rhs.activityInfo.name; + + return mCollator.compare(sa.toString().trim(), sb.toString().trim()); + }; + } + + @Override + public float getScore(ComponentName name) { + final ResolverTarget target = mTargetsDict.get(name); + if (target != null) { + return target.getSelectProbability(); + } + return 0; + } + + @Override + public void notifyOnTargetSelected(ComponentName componentName) { + if (mRanker != null) { + try { + int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet()) + .indexOf(componentName); + if (selectedPos >= 0 && mTargets != null) { + final float selectedProbability = getScore(componentName); + int order = 0; + for (ResolverTarget target : mTargets) { + if (target.getSelectProbability() > selectedProbability) { + order++; + } + } + logMetrics(order); + mRanker.train(mTargets, selectedPos); + } else { + if (DEBUG) { + Log.d(TAG, "Selected a unknown component: " + componentName); + } + } + } catch (RemoteException e) { + Log.e(TAG, "Error in Train: " + e); + } + } else { + if (DEBUG) { + Log.d(TAG, "Ranker is null; skip updateModel."); + } + } + } + + /** Records metrics for evaluation. */ + private void logMetrics(int selectedPos) { + if (mRankerServiceName != null) { + MetricsLogger metricsLogger = new MetricsLogger(); + LogMaker log = new LogMaker(MetricsEvent.ACTION_TARGET_SELECTED); + log.setComponentName(mRankerServiceName); + log.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, mAnnotationsUsed); + log.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION, selectedPos); + metricsLogger.write(log); + } + } + } } diff --git a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java index 04b888623732..3e640c1bad39 100644 --- a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java +++ b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java @@ -115,11 +115,6 @@ public class AbstractResolverComparatorTest { @Override void handleResultMessage(Message message) {} - - @Override - List<ComponentName> getTopComponentNames(int topK) { - return null; - } }; return testComparator; } diff --git a/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java b/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java new file mode 100644 index 000000000000..fbbe57c8e325 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java @@ -0,0 +1,61 @@ +/* + * Copyright 2022 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.internal.app; + +import android.content.ComponentName; +import android.content.pm.ResolveInfo; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * Basic {@link ResolverComparatorModel} implementation that sorts according to a pre-defined (or + * default) {@link java.util.Comparator}. + */ +public class FakeResolverComparatorModel implements ResolverComparatorModel { + private final Comparator<ResolveInfo> mComparator; + + public static FakeResolverComparatorModel makeModelFromComparator( + Comparator<ResolveInfo> comparator) { + return new FakeResolverComparatorModel(comparator); + } + + public static FakeResolverComparatorModel makeDefaultModel() { + return makeModelFromComparator(Comparator.comparing(ri -> ri.activityInfo.name)); + } + + @Override + public Comparator<ResolveInfo> getComparator() { + return mComparator; + } + + @Override + public float getScore(ComponentName name) { + return 0.0f; // Models are not required to provide numerical scores. + } + + @Override + public void notifyOnTargetSelected(ComponentName componentName) { + System.out.println( + "User selected " + componentName + " under model " + System.identityHashCode(this)); + } + + private FakeResolverComparatorModel(Comparator<ResolveInfo> comparator) { + mComparator = comparator; + } +}
\ No newline at end of file |