ResolverComparator changes to rank targets across profiles.

Adds methodology to rank targets across profiles, when
ResolverRankerService (AOSP) is used for comparison.

APSResolver and other files are touched only due to underlying
interface changes. There is no potential logic change there.

Bug: 240100378
Test: Manually verified by triggering sharesheet.
Change-Id: I7d27868d5a1012373d29824fef33688c00e8aeff
This commit is contained in:
Himanshu Gupta
2022-11-15 15:02:37 +00:00
parent 0d60b9871f
commit 411f4cb24c
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));
}