am b535c5e2: Merge "Start using some better sorting for intent resolution" into mnc-dev

* commit 'b535c5e28a7d689020461e9f9e8bed165eacc522':
  Start using some better sorting for intent resolution
This commit is contained in:
Adam Powell
2015-06-11 01:34:28 +00:00
committed by Android Git Automerger
2 changed files with 222 additions and 75 deletions

View File

@@ -18,8 +18,6 @@ package com.android.internal.app;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.os.AsyncTask;
import android.provider.Settings;
import android.text.TextUtils;
@@ -64,14 +62,11 @@ import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.widget.ResolverDrawerLayout;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
@@ -100,10 +95,7 @@ public class ResolverActivity extends Activity {
private boolean mResolvingHome = false;
private int mProfileSwitchMessageId = -1;
private final ArrayList<Intent> mIntents = new ArrayList<>();
private UsageStatsManager mUsm;
private Map<String, UsageStats> mStats;
private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
private ResolverComparator mResolverComparator;
private boolean mRegistered;
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@@ -222,10 +214,6 @@ public class ResolverActivity extends Activity {
}
mPm = getPackageManager();
mUsm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
final long sinceTime = System.currentTimeMillis() - USAGE_STATS_PERIOD;
mStats = mUsm.queryAndAggregateUsageStats(sinceTime, System.currentTimeMillis());
mPackageMonitor.register(this, getMainLooper(), false);
mRegistered = true;
@@ -236,6 +224,10 @@ public class ResolverActivity extends Activity {
// Add our initial intent as the first item, regardless of what else has already been added.
mIntents.add(0, new Intent(intent));
final String referrerPackage = getReferrerPackageName();
mResolverComparator = new ResolverComparator(this, getTargetIntent(), referrerPackage);
configureContentView(mIntents, initialIntents, rList, alwaysUseOption);
// Prevent the Resolver window from becoming the top fullscreen window and thus from taking
@@ -265,7 +257,6 @@ public class ResolverActivity extends Activity {
// Try to initialize the title icon if we have a view for it and a title to match
final ImageView titleIcon = (ImageView) findViewById(R.id.title_icon);
if (titleIcon != null) {
final String referrerPackage = getReferrerPackageName();
ApplicationInfo ai = null;
try {
if (!TextUtils.isEmpty(referrerPackage)) {
@@ -1175,8 +1166,8 @@ public class ResolverActivity extends Activity {
}
}
if (N > 1) {
Collections.sort(currentResolveList,
new ResolverComparator(ResolverActivity.this, getTargetIntent()));
mResolverComparator.compute(currentResolveList);
Collections.sort(currentResolveList, mResolverComparator);
}
// First put the initial items at the top.
if (mInitialIntents != null) {
@@ -1651,63 +1642,4 @@ public class ResolverActivity extends Activity {
&& match <= IntentFilter.MATCH_CATEGORY_PATH;
}
class ResolverComparator implements Comparator<ResolvedComponentInfo> {
private final Collator mCollator;
private final boolean mHttp;
public ResolverComparator(Context context, Intent intent) {
mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
String scheme = intent.getScheme();
mHttp = "http".equals(scheme) || "https".equals(scheme);
}
@Override
public int compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp) {
final ResolveInfo lhs = lhsp.getResolveInfoAt(0);
final ResolveInfo rhs = rhsp.getResolveInfoAt(0);
// We want to put the one targeted to another user at the end of the dialog.
if (lhs.targetUserId != UserHandle.USER_CURRENT) {
return 1;
}
if (mHttp) {
// Special case: we want filters that match URI paths/schemes to be
// ordered before others. This is for the case when opening URIs,
// to make native apps go above browsers.
final boolean lhsSpecific = isSpecificUriMatch(lhs.match);
final boolean rhsSpecific = isSpecificUriMatch(rhs.match);
if (lhsSpecific != rhsSpecific) {
return lhsSpecific ? -1 : 1;
}
}
if (mStats != null) {
final long timeDiff =
getPackageTimeSpent(rhs.activityInfo.packageName) -
getPackageTimeSpent(lhs.activityInfo.packageName);
if (timeDiff != 0) {
return timeDiff > 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(), sb.toString());
}
private long getPackageTimeSpent(String packageName) {
if (mStats != null) {
final UsageStats stats = mStats.get(packageName);
if (stats != null) {
return stats.getTotalTimeInForeground();
}
}
return 0;
}
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright (C) 2015 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.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Ranks and compares packages based on usage stats.
*/
class ResolverComparator implements Comparator<ResolvedComponentInfo> {
private static final String TAG = "ResolverComparator";
private static final boolean DEBUG = true;
// Two weeks
private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
private static final long RECENCY_TIME_PERIOD = 1000 * 60 * 60 * 12;
private static final float RECENCY_MULTIPLIER = 3.f;
private final Collator mCollator;
private final boolean mHttp;
private final PackageManager mPm;
private final UsageStatsManager mUsm;
private final Map<String, UsageStats> mStats;
private final long mCurrentTime;
private final long mSinceTime;
private final LinkedHashMap<ComponentName, ScoredTarget> mScoredTargets = new LinkedHashMap<>();
private final String mReferrerPackage;
public ResolverComparator(Context context, Intent intent, String referrerPackage) {
mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
String scheme = intent.getScheme();
mHttp = "http".equals(scheme) || "https".equals(scheme);
mReferrerPackage = referrerPackage;
mPm = context.getPackageManager();
mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
mCurrentTime = System.currentTimeMillis();
mSinceTime = mCurrentTime - USAGE_STATS_PERIOD;
mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime);
}
public void compute(List<ResolvedComponentInfo> targets) {
mScoredTargets.clear();
final long recentSinceTime = mCurrentTime - RECENCY_TIME_PERIOD;
long mostRecentlyUsedTime = recentSinceTime + 1;
long mostTimeSpent = 1;
int mostLaunched = 1;
for (ResolvedComponentInfo target : targets) {
final ScoredTarget scoredTarget
= new ScoredTarget(target.getResolveInfoAt(0).activityInfo);
mScoredTargets.put(target.name, scoredTarget);
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 long lastTimeUsed = pkStats.getLastTimeUsed();
scoredTarget.lastTimeUsed = lastTimeUsed;
if (lastTimeUsed > mostRecentlyUsedTime) {
mostRecentlyUsedTime = lastTimeUsed;
}
}
final long timeSpent = pkStats.getTotalTimeInForeground();
scoredTarget.timeSpent = timeSpent;
if (timeSpent > mostTimeSpent) {
mostTimeSpent = timeSpent;
}
final int launched = pkStats.mLaunchCount;
scoredTarget.launchCount = launched;
if (launched > mostLaunched) {
mostLaunched = launched;
}
}
}
if (DEBUG) {
Log.d(TAG, "compute - mostRecentlyUsedTime: " + mostRecentlyUsedTime
+ " mostTimeSpent: " + mostTimeSpent
+ " recentSinceTime: " + recentSinceTime
+ " mostLaunched: " + mostLaunched);
}
for (ScoredTarget target : mScoredTargets.values()) {
final float recency = (float) Math.max(target.lastTimeUsed - recentSinceTime, 0)
/ (mostRecentlyUsedTime - recentSinceTime);
final float recencyScore = recency * recency * RECENCY_MULTIPLIER;
final float usageTimeScore = (float) target.timeSpent / mostTimeSpent;
final float launchCountScore = (float) target.launchCount / mostLaunched;
target.score = recencyScore + usageTimeScore + launchCountScore;
if (DEBUG) {
Log.d(TAG, "Scores: recencyScore: " + recencyScore
+ " usageTimeScore: " + usageTimeScore
+ " launchCountScore: " + launchCountScore
+ " - " + target);
}
}
}
static boolean isPersistentProcess(ResolvedComponentInfo rci) {
if (rci != null && rci.getCount() > 0) {
return (rci.getResolveInfoAt(0).activityInfo.applicationInfo.flags &
ApplicationInfo.FLAG_PERSISTENT) != 0;
}
return false;
}
@Override
public int compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp) {
final ResolveInfo lhs = lhsp.getResolveInfoAt(0);
final ResolveInfo rhs = rhsp.getResolveInfoAt(0);
// We want to put the one targeted to another user at the end of the dialog.
if (lhs.targetUserId != UserHandle.USER_CURRENT) {
return 1;
}
if (mHttp) {
// Special case: we want filters that match URI paths/schemes to be
// ordered before others. This is for the case when opening URIs,
// to make native apps go above browsers.
final boolean lhsSpecific = ResolverActivity.isSpecificUriMatch(lhs.match);
final boolean rhsSpecific = ResolverActivity.isSpecificUriMatch(rhs.match);
if (lhsSpecific != rhsSpecific) {
return lhsSpecific ? -1 : 1;
}
}
if (mStats != null) {
final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
lhs.activityInfo.packageName, lhs.activityInfo.name));
final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName(
rhs.activityInfo.packageName, rhs.activityInfo.name));
final float diff = rhsTarget.score - lhsTarget.score;
if (diff != 0) {
return diff > 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());
}
static class ScoredTarget {
public final ComponentInfo componentInfo;
public float score;
public long lastTimeUsed;
public long timeSpent;
public long launchCount;
public ScoredTarget(ComponentInfo ci) {
componentInfo = ci;
}
@Override
public String toString() {
return "ScoredTarget{" + componentInfo
+ " score: " + score
+ " lastTimeUsed: " + lastTimeUsed
+ " timeSpent: " + timeSpent
+ " launchCount: " + launchCount
+ "}";
}
}
}