Merge "Create ResolverComparatorModel interface." into tm-dev am: ca46e52c30
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17913997 Change-Id: I86021827078bcf547a6b56cbdcb6f297e24ba693 Ignore-AOSP-First: this is an automerge Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,11 +115,6 @@ public class AbstractResolverComparatorTest {
|
||||
|
||||
@Override
|
||||
void handleResultMessage(Message message) {}
|
||||
|
||||
@Override
|
||||
List<ComponentName> getTopComponentNames(int topK) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
return testComparator;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user