Merge "ResolverComparator changes to rank targets across profiles."

This commit is contained in:
Himanshu Gupta
2023-01-18 21:32:52 +00:00
committed by Android (Google) Code Review
12 changed files with 310 additions and 166 deletions

View File

@@ -30,11 +30,16 @@ import android.os.UserHandle;
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.chooser.TargetInfo;
import com.google.android.collect.Lists;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Used to sort resolved activities in {@link ResolverListController}.
@@ -48,8 +53,8 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
private static final String TAG = "AbstractResolverComp";
protected AfterCompute mAfterCompute;
protected final PackageManager mPm;
protected final UsageStatsManager mUsm;
protected final Map<UserHandle, PackageManager> mPmMap = new HashMap<>();
protected final Map<UserHandle, UsageStatsManager> mUsmMap = new HashMap<>();
protected String[] mAnnotations;
protected String mContentType;
@@ -98,14 +103,28 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
}
};
public AbstractResolverComparator(Context context, Intent intent) {
// context here refers to the activity calling this comparator.
// targetUserSpace refers to the userSpace in which the targets to be ranked lie.
public AbstractResolverComparator(Context launchedFromContext, Intent intent,
UserHandle targetUserSpace) {
this(launchedFromContext, intent, Lists.newArrayList(targetUserSpace));
}
// context here refers to the activity calling this comparator.
// targetUserSpaceList refers to the userSpace(s) in which the targets to be ranked lie.
public AbstractResolverComparator(Context launchedFromContext, Intent intent,
List<UserHandle> targetUserSpaceList) {
String scheme = intent.getScheme();
mHttp = "http".equals(scheme) || "https".equals(scheme);
mContentType = intent.getType();
getContentAnnotations(intent);
mPm = context.getPackageManager();
mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
mAzComparator = new AzInfoComparator(context);
for (UserHandle user : targetUserSpaceList) {
Context userContext = launchedFromContext.createContextAsUser(user, 0);
mPmMap.put(user, userContext.getPackageManager());
mUsmMap.put(user,
(UsageStatsManager) userContext.getSystemService(Context.USAGE_STATS_SERVICE));
}
mAzComparator = new AzInfoComparator(launchedFromContext);
}
// get annotations of content from intent.
@@ -208,8 +227,8 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
/**
* Computes features for each target. This will be called before calls to {@link
* #getScore(ComponentName)} or {@link #compare(Object, Object)}, in order to prepare the
* comparator for those calls. Note that {@link #getScore(ComponentName)} uses {@link
* #getScore(TargetInfo)} or {@link #compare(ResolveInfo, ResolveInfo)}, in order to prepare the
* comparator for those calls. Note that {@link #getScore(TargetInfo)} uses {@link
* ComponentName}, so the implementation will have to be prepared to identify a {@link
* ResolvedComponentInfo} by {@link ComponentName}. {@link #beforeCompute()} will be called
* before doing any computing.
@@ -226,7 +245,7 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
* Returns the score that was calculated for the corresponding {@link ResolvedComponentInfo}
* when {@link #compute(List)} was called before this.
*/
abstract float getScore(ComponentName name);
abstract float getScore(TargetInfo targetInfo);
/** Handles result message sent to mHandler. */
abstract void handleResultMessage(Message message);
@@ -234,9 +253,11 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
/**
* Reports to UsageStats what was chosen.
*/
final void updateChooserCounts(String packageName, int userId, String action) {
if (mUsm != null) {
mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
final void updateChooserCounts(String packageName, UserHandle user, String action) {
if (mUsmMap.containsKey(user)) {
mUsmMap.get(user)
.reportChooserSelection(packageName, user.getIdentifier(), mContentType,
mAnnotations, action);
}
}
@@ -246,9 +267,9 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
* <p>Default implementation does nothing, as we could have simple model that does not train
* online.
*
* @param componentName the component that the user clicked
* @param targetInfo the target that the user clicked.
*/
void updateModel(ComponentName componentName) {
void updateModel(TargetInfo targetInfo) {
}
/** Called before {@link #doCompute(List)}. Sets up 500ms timeout. */

View File

@@ -31,6 +31,9 @@ import android.os.UserHandle;
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.chooser.TargetInfo;
import com.google.android.collect.Lists;
import java.util.ArrayList;
import java.util.Collections;
@@ -70,7 +73,7 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
AppPredictor appPredictor,
UserHandle user,
ChooserActivityLogger chooserActivityLogger) {
super(context, intent);
super(context, intent, Lists.newArrayList(user));
mContext = context;
mIntent = intent;
mAppPredictor = appPredictor;
@@ -99,13 +102,13 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
}
@Override
float getScore(ComponentName name) {
return mComparatorModel.getScore(name);
float getScore(TargetInfo targetInfo) {
return mComparatorModel.getScore(targetInfo);
}
@Override
void updateModel(ComponentName componentName) {
mComparatorModel.notifyOnTargetSelected(componentName);
void updateModel(TargetInfo targetInfo) {
mComparatorModel.notifyOnTargetSelected(targetInfo);
}
@Override
@@ -158,9 +161,12 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
private void setupFallbackModel(List<ResolvedComponentInfo> targets) {
mResolverRankerService =
new ResolverRankerServiceResolverComparator(
mContext, mIntent, mReferrerPackage,
mContext,
mIntent,
mReferrerPackage,
() -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT),
getChooserActivityLogger());
getChooserActivityLogger(),
mUser);
mComparatorModel = mModelBuilder.buildFallbackModel(mResolverRankerService);
mResolverRankerService.compute(targets);
}
@@ -224,13 +230,13 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
}
@Override
public float getScore(ComponentName componentName) {
return comparator.getScore(componentName);
public float getScore(TargetInfo targetInfo) {
return comparator.getScore(targetInfo);
}
@Override
public void notifyOnTargetSelected(ComponentName componentName) {
comparator.updateModel(componentName);
public void notifyOnTargetSelected(TargetInfo targetInfo) {
comparator.updateModel(targetInfo);
}
};
}
@@ -271,8 +277,8 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
}
@Override
public float getScore(ComponentName name) {
Integer rank = mTargetRanks.get(name);
public float getScore(TargetInfo targetInfo) {
Integer rank = mTargetRanks.get(targetInfo.getResolvedComponentName());
if (rank == null) {
Log.w(TAG, "Score requested for unknown component. Did you call compute yet?");
return 0f;
@@ -282,13 +288,14 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
}
@Override
public void notifyOnTargetSelected(ComponentName componentName) {
public void notifyOnTargetSelected(TargetInfo targetInfo) {
mAppPredictor.notifyAppTargetEvent(
new AppTargetEvent.Builder(
new AppTarget.Builder(
new AppTargetId(componentName.toString()),
componentName.getPackageName(), mUser)
.setClassName(componentName.getClassName()).build(),
new AppTargetId(targetInfo.getResolvedComponentName().toString()),
targetInfo.getResolvedComponentName().getPackageName(), mUser)
.setClassName(targetInfo.getResolvedComponentName()
.getClassName()).build(),
ACTION_LAUNCH).build());
}
}

View File

@@ -2268,9 +2268,11 @@ public class ChooserActivity extends ResolverActivity implements
mChooserMultiProfilePagerAdapter.getActiveListAdapter();
if (currentListAdapter != null) {
sendImpressionToAppPredictor(info, currentListAdapter);
currentListAdapter.updateModel(info.getResolvedComponentName());
currentListAdapter.updateChooserCounts(ri.activityInfo.packageName,
targetIntent.getAction());
currentListAdapter.updateModel(info);
currentListAdapter.updateChooserCounts(
ri.activityInfo.packageName,
targetIntent.getAction(),
ri.userHandle);
}
if (DEBUG) {
Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
@@ -2395,7 +2397,10 @@ public class ChooserActivity extends ResolverActivity implements
*/
@Nullable
private AppPredictor getAppPredictorForShareActivitiesIfEnabled(UserHandle userHandle) {
return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? createAppPredictor(userHandle) : null;
// We cannot use APS service when clone profile is present as APS service cannot sort
// cross profile targets as of now.
return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES && getCloneProfileUserHandle() == null
? createAppPredictor(userHandle) : null;
}
void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
@@ -2548,8 +2553,13 @@ public class ChooserActivity extends ResolverActivity implements
getReferrerPackageName(), appPredictor, userHandle, getChooserActivityLogger());
} else {
resolverComparator =
new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
getReferrerPackageName(), null, getChooserActivityLogger());
new ResolverRankerServiceResolverComparator(
this,
getTargetIntent(),
getReferrerPackageName(),
null,
getChooserActivityLogger(),
getResolverRankerServiceUserHandleList(userHandle));
}
UserHandle queryIntentsUser = getQueryIntentsUser(userHandle);

View File

@@ -1617,6 +1617,14 @@ public class ResolverActivity extends Activity implements
@VisibleForTesting
protected ResolverListController createListController(UserHandle userHandle) {
UserHandle queryIntentsUser = getQueryIntentsUser(userHandle);
ResolverRankerServiceResolverComparator resolverComparator =
new ResolverRankerServiceResolverComparator(
this,
getTargetIntent(),
getReferrerPackageName(),
null,
null,
getResolverRankerServiceUserHandleList(userHandle));
return new ResolverListController(
this,
mPm,
@@ -1624,6 +1632,7 @@ public class ResolverActivity extends Activity implements
getReferrerPackageName(),
mLaunchedFromUid,
userHandle,
resolverComparator,
queryIntentsUser);
}
@@ -2534,4 +2543,21 @@ public class ResolverActivity extends Activity implements
UserHandle predictedHandle) {
return resolveInfo.userHandle;
}
/**
* Returns the {@link List} of {@link UserHandle} to pass on to the
* {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}.
*/
protected final List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) {
List<UserHandle> userList = new ArrayList<>();
userList.add(userHandle);
// Add clonedProfileUserHandle to the list only if we are:
// a. Building the Personal Tab.
// b. CloneProfile exists on the device.
if (userHandle.equals(getPersonalProfileUserHandle())
&& getCloneProfileUserHandle() != null) {
userList.add(getCloneProfileUserHandle());
}
return userList;
}
}

View File

@@ -16,11 +16,11 @@
package com.android.internal.app;
import android.content.ComponentName;
import android.content.pm.ResolveInfo;
import com.android.internal.app.chooser.TargetInfo;
import java.util.Comparator;
import java.util.List;
/**
* A ranking model for resolver targets, providing ordering and (optionally) numerical scoring.
@@ -45,7 +45,7 @@ interface ResolverComparatorModel {
* 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);
float getScore(TargetInfo targetInfo);
/**
* Notify the model that the user selected a target. (Models may log this information, use it as
@@ -53,5 +53,5 @@ interface ResolverComparatorModel {
* {@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);
void notifyOnTargetSelected(TargetInfo targetInfo);
}

View File

@@ -21,7 +21,6 @@ import static android.content.Context.ACTIVITY_SERVICE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.PermissionChecker;
@@ -157,17 +156,17 @@ public class ResolverListAdapter extends BaseAdapter {
/**
* Returns the app share score of the given {@code componentName}.
*/
public float getScore(ComponentName componentName) {
return mResolverListController.getScore(componentName);
public float getScore(TargetInfo targetInfo) {
return mResolverListController.getScore(targetInfo);
}
public void updateModel(ComponentName componentName) {
mResolverListController.updateModel(componentName);
public void updateModel(TargetInfo targetInfo) {
mResolverListController.updateModel(targetInfo);
}
public void updateChooserCounts(String packageName, String action) {
public void updateChooserCounts(String packageName, String action, UserHandle userHandle) {
mResolverListController.updateChooserCounts(
packageName, getUserHandle().getIdentifier(), action);
packageName, userHandle, action);
}
List<ResolvedComponentInfo> getUnfilteredResolveList() {

View File

@@ -33,6 +33,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.app.chooser.TargetInfo;
import java.util.ArrayList;
import java.util.Collections;
@@ -72,7 +73,13 @@ public class ResolverListController {
UserHandle queryIntentsAsUser) {
this(context, pm, targetIntent, referrerPackage, launchedFromUid, userHandle,
new ResolverRankerServiceResolverComparator(
context, targetIntent, referrerPackage, null, null), queryIntentsAsUser);
context,
targetIntent,
referrerPackage,
null,
null,
userHandle),
queryIntentsAsUser);
}
public ResolverListController(
@@ -397,22 +404,22 @@ public class ResolverListController {
@VisibleForTesting
public float getScore(DisplayResolveInfo target) {
return mResolverComparator.getScore(target.getResolvedComponentName());
return mResolverComparator.getScore(target);
}
/**
* Returns the app share score of the given {@code componentName}.
*/
public float getScore(ComponentName componentName) {
return mResolverComparator.getScore(componentName);
public float getScore(TargetInfo targetInfo) {
return mResolverComparator.getScore(targetInfo);
}
public void updateModel(ComponentName componentName) {
mResolverComparator.updateModel(componentName);
public void updateModel(TargetInfo targetInfo) {
mResolverComparator.updateModel(targetInfo);
}
public void updateChooserCounts(String packageName, int userId, String action) {
mResolverComparator.updateChooserCounts(packageName, userId, action);
public void updateChooserCounts(String packageName, UserHandle user, String action) {
mResolverComparator.updateChooserCounts(packageName, user, action);
}
public void destroy() {

View File

@@ -17,11 +17,13 @@
package com.android.internal.app;
import android.annotation.Nullable;
import android.app.usage.UsageStats;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -38,12 +40,16 @@ import android.service.resolver.ResolverTarget;
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.google.android.collect.Lists;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -69,10 +75,10 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
private static final int CONNECTION_COST_TIMEOUT_MILLIS = 200;
private final Collator mCollator;
private final Map<String, UsageStats> mStats;
private final Map<UserHandle, Map<String, UsageStats>> mStatsPerUser;
private final long mCurrentTime;
private final long mSinceTime;
private final LinkedHashMap<ComponentName, ResolverTarget> mTargetsDict = new LinkedHashMap<>();
private final Map<UserHandle, LinkedHashMap<ComponentName, ResolverTarget>> mTargetsDictPerUser;
private final String mReferrerPackage;
private final Object mLock = new Object();
private ArrayList<ResolverTarget> mTargets;
@@ -85,17 +91,34 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
private CountDownLatch mConnectSignal;
private ResolverRankerServiceComparatorModel mComparatorModel;
public ResolverRankerServiceResolverComparator(Context context, Intent intent,
// context here refers to the activity calling this comparator.
// targetUserSpace refers to the userSpace in which the targets to be ranked lie.
public ResolverRankerServiceResolverComparator(Context launchedFromContext, Intent intent,
String referrerPackage, AfterCompute afterCompute,
ChooserActivityLogger chooserActivityLogger) {
super(context, intent);
mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
mReferrerPackage = referrerPackage;
mContext = context;
ChooserActivityLogger chooserActivityLogger, UserHandle targetUserSpace) {
this(launchedFromContext, intent, referrerPackage, afterCompute, chooserActivityLogger,
Lists.newArrayList(targetUserSpace));
}
// context here refers to the activity calling this comparator.
// targetUserSpaceList refers to the userSpace(s) in which the targets to be ranked lie.
public ResolverRankerServiceResolverComparator(Context launchedFromContext, Intent intent,
String referrerPackage, AfterCompute afterCompute,
ChooserActivityLogger chooserActivityLogger, List<UserHandle> targetUserSpaceList) {
super(launchedFromContext, intent, targetUserSpaceList);
mCollator = Collator.getInstance(launchedFromContext
.getResources().getConfiguration().locale);
mReferrerPackage = referrerPackage;
mContext = launchedFromContext;
mCurrentTime = System.currentTimeMillis();
mSinceTime = mCurrentTime - USAGE_STATS_PERIOD;
mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime);
mStatsPerUser = new HashMap<>();
mTargetsDictPerUser = new HashMap<>();
for (UserHandle user : targetUserSpaceList) {
mStatsPerUser.put(user, mUsmMap.get(user)
.queryAndAggregateUsageStats(mSinceTime, mCurrentTime));
mTargetsDictPerUser.put(user, new LinkedHashMap<>());
}
mAction = intent.getAction();
mRankerServiceName = new ComponentName(mContext, this.getClass());
setCallBack(afterCompute);
@@ -147,57 +170,63 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
for (ResolvedComponentInfo target : targets) {
final ResolverTarget resolverTarget = new ResolverTarget();
mTargetsDict.put(target.name, resolverTarget);
final UsageStats pkStats = mStats.get(target.name.getPackageName());
if (pkStats != null) {
// Only count recency for apps that weren't the caller
// since the caller is always the most recent.
// Persistent processes muck this up, so omit them too.
if (!target.name.getPackageName().equals(mReferrerPackage)
&& !isPersistentProcess(target)) {
final float recencyScore =
(float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0);
resolverTarget.setRecencyScore(recencyScore);
if (recencyScore > mostRecencyScore) {
mostRecencyScore = recencyScore;
}
}
final float timeSpentScore = (float) pkStats.getTotalTimeInForeground();
resolverTarget.setTimeSpentScore(timeSpentScore);
if (timeSpentScore > mostTimeSpentScore) {
mostTimeSpentScore = timeSpentScore;
}
final float launchScore = (float) pkStats.mLaunchCount;
resolverTarget.setLaunchScore(launchScore);
if (launchScore > mostLaunchScore) {
mostLaunchScore = launchScore;
}
float chooserScore = 0.0f;
if (pkStats.mChooserCounts != null && mAction != null
&& pkStats.mChooserCounts.get(mAction) != null) {
chooserScore = (float) pkStats.mChooserCounts.get(mAction)
.getOrDefault(mContentType, 0);
if (mAnnotations != null) {
final int size = mAnnotations.length;
for (int i = 0; i < size; i++) {
chooserScore += (float) pkStats.mChooserCounts.get(mAction)
.getOrDefault(mAnnotations[i], 0);
final LinkedHashMap<ComponentName, ResolverTarget> targetsDict = mTargetsDictPerUser
.get(target.getResolveInfoAt(0).userHandle);
final Map<String, UsageStats> stats = mStatsPerUser
.get(target.getResolveInfoAt(0).userHandle);
if (targetsDict != null && stats != null) {
targetsDict.put(target.name, resolverTarget);
final UsageStats pkStats = stats.get(target.name.getPackageName());
if (pkStats != null) {
// Only count recency for apps that weren't the caller
// since the caller is always the most recent.
// Persistent processes muck this up, so omit them too.
if (!target.name.getPackageName().equals(mReferrerPackage)
&& !isPersistentProcess(target)) {
final float recencyScore =
(float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0);
resolverTarget.setRecencyScore(recencyScore);
if (recencyScore > mostRecencyScore) {
mostRecencyScore = recencyScore;
}
}
}
if (DEBUG) {
if (mAction == null) {
Log.d(TAG, "Action type is null");
} else {
Log.d(TAG, "Chooser Count of " + mAction + ":" +
target.name.getPackageName() + " is " +
Float.toString(chooserScore));
final float timeSpentScore = (float) pkStats.getTotalTimeInForeground();
resolverTarget.setTimeSpentScore(timeSpentScore);
if (timeSpentScore > mostTimeSpentScore) {
mostTimeSpentScore = timeSpentScore;
}
final float launchScore = (float) pkStats.mLaunchCount;
resolverTarget.setLaunchScore(launchScore);
if (launchScore > mostLaunchScore) {
mostLaunchScore = launchScore;
}
float chooserScore = 0.0f;
if (pkStats.mChooserCounts != null && mAction != null
&& pkStats.mChooserCounts.get(mAction) != null) {
chooserScore = (float) pkStats.mChooserCounts.get(mAction)
.getOrDefault(mContentType, 0);
if (mAnnotations != null) {
final int size = mAnnotations.length;
for (int i = 0; i < size; i++) {
chooserScore += (float) pkStats.mChooserCounts.get(mAction)
.getOrDefault(mAnnotations[i], 0);
}
}
}
if (DEBUG) {
if (mAction == null) {
Log.d(TAG, "Action type is null");
} else {
Log.d(TAG, "Chooser Count of " + mAction + ":"
+ target.name.getPackageName() + " is "
+ Float.toString(chooserScore));
}
}
resolverTarget.setChooserScore(chooserScore);
if (chooserScore > mostChooserScore) {
mostChooserScore = chooserScore;
}
}
resolverTarget.setChooserScore(chooserScore);
if (chooserScore > mostChooserScore) {
mostChooserScore = chooserScore;
}
}
}
@@ -209,7 +238,11 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
+ " mostChooserScore: " + mostChooserScore);
}
mTargets = new ArrayList<>(mTargetsDict.values());
mTargets = new ArrayList<>();
for (UserHandle u : mTargetsDictPerUser.keySet()) {
mTargets.addAll(mTargetsDictPerUser.get(u).values());
}
for (ResolverTarget target : mTargets) {
final float recency = target.getRecencyScore() / mostRecencyScore;
setFeatures(target, recency * recency * RECENCY_MULTIPLIER,
@@ -232,15 +265,15 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
}
@Override
public float getScore(ComponentName name) {
return mComparatorModel.getScore(name);
public float getScore(TargetInfo targetInfo) {
return mComparatorModel.getScore(targetInfo);
}
// update ranking model when the connection to it is valid.
@Override
public void updateModel(ComponentName componentName) {
public void updateModel(TargetInfo targetInfo) {
synchronized (mLock) {
mComparatorModel.notifyOnTargetSelected(componentName);
mComparatorModel.notifyOnTargetSelected(targetInfo);
}
}
@@ -281,7 +314,8 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
// resolve the service for ranking.
private Intent resolveRankerService() {
Intent intent = new Intent(ResolverRankerService.SERVICE_INTERFACE);
final List<ResolveInfo> resolveInfos = mPm.queryIntentServices(intent, 0);
final List<ResolveInfo> resolveInfos = mContext.getPackageManager()
.queryIntentServices(intent, 0);
for (ResolveInfo resolveInfo : resolveInfos) {
if (resolveInfo == null || resolveInfo.serviceInfo == null
|| resolveInfo.serviceInfo.applicationInfo == null) {
@@ -294,7 +328,8 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
resolveInfo.serviceInfo.applicationInfo.packageName,
resolveInfo.serviceInfo.name);
try {
final String perm = mPm.getServiceInfo(componentName, 0).permission;
final String perm = mContext.getPackageManager()
.getServiceInfo(componentName, 0).permission;
if (!ResolverRankerService.BIND_PERMISSION.equals(perm)) {
Log.w(TAG, "ResolverRankerService " + componentName + " does not require"
+ " permission " + ResolverRankerService.BIND_PERMISSION
@@ -305,9 +340,9 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
+ " in the manifest.");
continue;
}
if (PackageManager.PERMISSION_GRANTED != mPm.checkPermission(
ResolverRankerService.HOLD_PERMISSION,
resolveInfo.serviceInfo.packageName)) {
if (PackageManager.PERMISSION_GRANTED != mContext.getPackageManager()
.checkPermission(ResolverRankerService.HOLD_PERMISSION,
resolveInfo.serviceInfo.packageName)) {
Log.w(TAG, "ResolverRankerService " + componentName + " does not hold"
+ " permission " + ResolverRankerService.HOLD_PERMISSION
+ " - this service will not be queried for "
@@ -385,7 +420,9 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
@Override
void beforeCompute() {
super.beforeCompute();
mTargetsDict.clear();
for (UserHandle userHandle : mTargetsDictPerUser.keySet()) {
mTargetsDictPerUser.get(userHandle).clear();
}
mTargets = null;
mRankerServiceName = new ComponentName(mContext, this.getClass());
mComparatorModel = buildUpdatedModel();
@@ -465,14 +502,14 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
// 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,
mStatsPerUser,
mTargetsDictPerUser,
mTargets,
mCollator,
mRanker,
mRankerServiceName,
(mAnnotations != null),
mPm);
mPmMap);
}
/**
@@ -481,35 +518,36 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
* 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 Map<UserHandle, Map<String, UsageStats>> mStatsPerUser; // Treat as immutable.
private final Map<UserHandle, LinkedHashMap<ComponentName,
ResolverTarget>> mTargetsDictPerUser; // 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;
private final Map<UserHandle, PackageManager> mPmMap;
// 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,
Map<UserHandle, Map<String, UsageStats>> statsPerUser,
Map<UserHandle, LinkedHashMap<ComponentName, ResolverTarget>> targetsDictPerUser,
List<ResolverTarget> targets,
Collator collator,
IResolverRankerService ranker,
ComponentName rankerServiceName,
boolean annotationsUsed,
PackageManager pm) {
mStats = stats;
mTargetsDict = targetsDict;
Map<UserHandle, PackageManager> pmMap) {
mStatsPerUser = statsPerUser;
mTargetsDictPerUser = targetsDictPerUser;
mTargets = targets;
mCollator = collator;
mRanker = ranker;
mRankerServiceName = rankerServiceName;
mAnnotationsUsed = annotationsUsed;
mPm = pm;
mPmMap = pmMap;
}
@Override
@@ -518,25 +556,29 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
// 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));
final ResolverTarget lhsTarget = getActivityResolverTargetForUser(lhs.activityInfo,
lhs.userHandle);
final ResolverTarget rhsTarget = getActivityResolverTargetForUser(rhs.activityInfo,
rhs.userHandle);
if (lhsTarget != null && rhsTarget != null) {
final int selectProbabilityDiff = Float.compare(
rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
if (lhsTarget != null && rhsTarget != null) {
final int selectProbabilityDiff = Float.compare(
rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
if (selectProbabilityDiff != 0) {
return selectProbabilityDiff > 0 ? 1 : -1;
}
if (selectProbabilityDiff != 0) {
return selectProbabilityDiff > 0 ? 1 : -1;
}
}
CharSequence sa = lhs.loadLabel(mPm);
CharSequence sa = null;
if (mPmMap.containsKey(lhs.userHandle)) {
sa = lhs.loadLabel(mPmMap.get(lhs.userHandle));
}
if (sa == null) sa = lhs.activityInfo.name;
CharSequence sb = rhs.loadLabel(mPm);
CharSequence sb = null;
if (mPmMap.containsKey(rhs.userHandle)) {
sb = rhs.loadLabel(mPmMap.get(rhs.userHandle));
}
if (sb == null) sb = rhs.activityInfo.name;
return mCollator.compare(sa.toString().trim(), sb.toString().trim());
@@ -544,22 +586,28 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
}
@Override
public float getScore(ComponentName name) {
final ResolverTarget target = mTargetsDict.get(name);
if (target != null) {
return target.getSelectProbability();
public float getScore(TargetInfo targetInfo) {
if (mTargetsDictPerUser.containsKey(targetInfo.getResolveInfo().userHandle)
&& mTargetsDictPerUser.get(targetInfo.getResolveInfo().userHandle)
.get(targetInfo.getResolvedComponentName()) != null) {
return mTargetsDictPerUser.get(targetInfo.getResolveInfo().userHandle)
.get(targetInfo.getResolvedComponentName()).getSelectProbability();
}
return 0;
}
@Override
public void notifyOnTargetSelected(ComponentName componentName) {
public void notifyOnTargetSelected(TargetInfo targetInfo) {
if (mRanker != null) {
try {
int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet())
.indexOf(componentName);
int selectedPos = -1;
if (mTargetsDictPerUser.containsKey(targetInfo.getResolveInfo().userHandle)) {
selectedPos = new ArrayList<>(mTargetsDictPerUser
.get(targetInfo.getResolveInfo().userHandle).keySet())
.indexOf(targetInfo.getResolvedComponentName());
}
if (selectedPos >= 0 && mTargets != null) {
final float selectedProbability = getScore(componentName);
final float selectedProbability = getScore(targetInfo);
int order = 0;
for (ResolverTarget target : mTargets) {
if (target.getSelectProbability() > selectedProbability) {
@@ -570,7 +618,8 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
mRanker.train(mTargets, selectedPos);
} else {
if (DEBUG) {
Log.d(TAG, "Selected a unknown component: " + componentName);
Log.d(TAG, "Selected a unknown component: " + targetInfo
.getResolvedComponentName());
}
}
} catch (RemoteException e) {
@@ -594,5 +643,16 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
metricsLogger.write(log);
}
}
@Nullable
private ResolverTarget getActivityResolverTargetForUser(
ActivityInfo activity, UserHandle user) {
if ((mStatsPerUser == null) || !mTargetsDictPerUser.containsKey(user)) {
return null;
}
return mTargetsDictPerUser
.get(user)
.get(new ComponentName(activity.packageName, activity.name));
}
}
}

View File

@@ -18,6 +18,7 @@ package com.android.internal.app;
import static junit.framework.Assert.assertEquals;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -27,6 +28,10 @@ import android.os.Message;
import androidx.test.InstrumentationRegistry;
import com.android.internal.app.chooser.TargetInfo;
import com.google.android.collect.Lists;
import org.junit.Test;
import java.util.List;
@@ -96,7 +101,8 @@ public class AbstractResolverComparatorTest {
Intent intent = new Intent();
AbstractResolverComparator testComparator =
new AbstractResolverComparator(context, intent) {
new AbstractResolverComparator(context, intent,
Lists.newArrayList(context.getUser())) {
@Override
int compare(ResolveInfo lhs, ResolveInfo rhs) {
@@ -109,7 +115,7 @@ public class AbstractResolverComparatorTest {
void doCompute(List<ResolverActivity.ResolvedComponentInfo> targets) {}
@Override
float getScore(ComponentName name) {
float getScore(TargetInfo targetInfo) {
return 0;
}

View File

@@ -545,13 +545,17 @@ public class ChooserActivityTest {
return true;
};
ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
DisplayResolveInfo testDri =
activity.createTestDisplayResolveInfo(sendIntent, toChoose, "testLabel", "testInfo",
sendIntent,/* resolveInfoPresentationGetter */ null);
onView(withText(toChoose.activityInfo.name))
.perform(click());
waitForIdle();
verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
.updateChooserCounts(Mockito.anyString(), anyInt(), Mockito.anyString());
.updateChooserCounts(Mockito.anyString(), any(UserHandle.class),
Mockito.anyString());
verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
.updateModel(toChoose.activityInfo.getComponentName());
.updateModel(testDri);
assertThat(activity.getIsSelected(), is(true));
}

View File

@@ -19,6 +19,8 @@ package com.android.internal.app;
import android.content.ComponentName;
import android.content.pm.ResolveInfo;
import com.android.internal.app.chooser.TargetInfo;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -45,14 +47,15 @@ public class FakeResolverComparatorModel implements ResolverComparatorModel {
}
@Override
public float getScore(ComponentName name) {
public float getScore(TargetInfo targetInfo) {
return 0.0f; // Models are not required to provide numerical scores.
}
@Override
public void notifyOnTargetSelected(ComponentName componentName) {
public void notifyOnTargetSelected(TargetInfo targetInfo) {
System.out.println(
"User selected " + componentName + " under model " + System.identityHashCode(this));
"User selected " + targetInfo.getResolvedComponentName() + " under model "
+ System.identityHashCode(this));
}
private FakeResolverComparatorModel(Comparator<ResolveInfo> comparator) {

View File

@@ -86,6 +86,7 @@ public class ResolverListControllerTest {
config.locale = Locale.getDefault();
List<ResolveInfo> services = new ArrayList<>();
mUsm = new UsageStatsManager(mMockContext, mMockService);
when(mMockContext.createContextAsUser(any(), anyInt())).thenReturn(mMockContext);
when(mMockContext.getSystemService(Context.USAGE_STATS_SERVICE)).thenReturn(mUsm);
when(mMockPackageManager.queryIntentServices(any(), anyInt())).thenReturn(services);
when(mMockResources.getConfiguration()).thenReturn(config);
@@ -126,7 +127,7 @@ public class ResolverListControllerTest {
UserHandle.SYSTEM);
mController.sort(new ArrayList<ResolvedComponentInfo>());
long beforeReport = getCount(mUsm, packageName, action, annotation);
mController.updateChooserCounts(packageName, UserHandle.USER_CURRENT, action);
mController.updateChooserCounts(packageName, UserHandle.SYSTEM, action);
long afterReport = getCount(mUsm, packageName, action, annotation);
assertThat(afterReport, is(beforeReport + 1l));
}