diff options
author | Adam Powell <adamp@google.com> | 2015-06-11 01:16:55 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-06-11 01:16:57 +0000 |
commit | b535c5e28a7d689020461e9f9e8bed165eacc522 (patch) | |
tree | 655a20d302e56b96cd83c99fc3ed1e2fd0623c55 | |
parent | fb48b0142467b9edac7dd9cc4322234aafc754b3 (diff) | |
parent | d25267c0d81e84a064faf281a61c64eec3facf68 (diff) | |
download | frameworks_base-b535c5e28a7d689020461e9f9e8bed165eacc522.zip frameworks_base-b535c5e28a7d689020461e9f9e8bed165eacc522.tar.gz frameworks_base-b535c5e28a7d689020461e9f9e8bed165eacc522.tar.bz2 |
Merge "Start using some better sorting for intent resolution" into mnc-dev
-rw-r--r-- | core/java/com/android/internal/app/ResolverActivity.java | 82 | ||||
-rw-r--r-- | core/java/com/android/internal/app/ResolverComparator.java | 215 |
2 files changed, 222 insertions, 75 deletions
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index ba4af89..39c86f9 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -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; - } - } } diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java new file mode 100644 index 0000000..42668f1 --- /dev/null +++ b/core/java/com/android/internal/app/ResolverComparator.java @@ -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 + + "}"; + } + } +} |