1d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell/* 2d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * Copyright (C) 2015 The Android Open Source Project 3d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * 4d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * Licensed under the Apache License, Version 2.0 (the "License"); 5d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * you may not use this file except in compliance with the License. 6d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * You may obtain a copy of the License at 7d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * 8d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * http://www.apache.org/licenses/LICENSE-2.0 9d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * 10d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * Unless required by applicable law or agreed to in writing, software 11d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * distributed under the License is distributed on an "AS IS" BASIS, 12d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * See the License for the specific language governing permissions and 14d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * limitations under the License. 15d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell */ 16d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 17d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 18d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellpackage com.android.internal.app; 19d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 20d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport android.app.usage.UsageStats; 21d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport android.app.usage.UsageStatsManager; 22d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport android.content.ComponentName; 23d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport android.content.Context; 24d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport android.content.Intent; 25d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport android.content.IntentFilter; 26d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport android.content.pm.ApplicationInfo; 27d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport android.content.pm.ComponentInfo; 28d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport android.content.pm.PackageManager; 29d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport android.content.pm.ResolveInfo; 30d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport android.os.UserHandle; 31d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport android.text.TextUtils; 32d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport android.util.Log; 33d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport com.android.internal.app.ResolverActivity.ResolvedComponentInfo; 34d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 35d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport java.text.Collator; 36d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport java.util.ArrayList; 37d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport java.util.Comparator; 38d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport java.util.LinkedHashMap; 39d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport java.util.List; 40d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellimport java.util.Map; 41d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 42d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell/** 43d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell * Ranks and compares packages based on usage stats. 44d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell */ 45d25267c0d81e84a064faf281a61c64eec3facf68Adam Powellclass ResolverComparator implements Comparator<ResolvedComponentInfo> { 46d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell private static final String TAG = "ResolverComparator"; 47d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 48064139434da2d7a790bb0e7ea377dc176cd9a6eeAdam Powell private static final boolean DEBUG = false; 49d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 502388251fcd8def03a6bbc91382e84db085cf4253Adam Powell // One week 512388251fcd8def03a6bbc91382e84db085cf4253Adam Powell private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 7; 52d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 53d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell private static final long RECENCY_TIME_PERIOD = 1000 * 60 * 60 * 12; 54d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 55bba0030a8529fd904056cbb7cfbbe3afbc1c8a95Adam Powell private static final float RECENCY_MULTIPLIER = 2.f; 56d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 57d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell private final Collator mCollator; 58d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell private final boolean mHttp; 59d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell private final PackageManager mPm; 60d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell private final UsageStatsManager mUsm; 61d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell private final Map<String, UsageStats> mStats; 62d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell private final long mCurrentTime; 63d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell private final long mSinceTime; 64d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell private final LinkedHashMap<ComponentName, ScoredTarget> mScoredTargets = new LinkedHashMap<>(); 65d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell private final String mReferrerPackage; 66d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 67d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell public ResolverComparator(Context context, Intent intent, String referrerPackage) { 68d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell mCollator = Collator.getInstance(context.getResources().getConfiguration().locale); 69d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell String scheme = intent.getScheme(); 70d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell mHttp = "http".equals(scheme) || "https".equals(scheme); 71d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell mReferrerPackage = referrerPackage; 72d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 73d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell mPm = context.getPackageManager(); 74d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); 75d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 76d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell mCurrentTime = System.currentTimeMillis(); 77d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell mSinceTime = mCurrentTime - USAGE_STATS_PERIOD; 78d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime); 79d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 80d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 81d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell public void compute(List<ResolvedComponentInfo> targets) { 82d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell mScoredTargets.clear(); 83d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 84d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final long recentSinceTime = mCurrentTime - RECENCY_TIME_PERIOD; 85d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 86d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell long mostRecentlyUsedTime = recentSinceTime + 1; 87d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell long mostTimeSpent = 1; 88d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell int mostLaunched = 1; 89d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 90d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell for (ResolvedComponentInfo target : targets) { 91d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final ScoredTarget scoredTarget 92d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell = new ScoredTarget(target.getResolveInfoAt(0).activityInfo); 93d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell mScoredTargets.put(target.name, scoredTarget); 94d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final UsageStats pkStats = mStats.get(target.name.getPackageName()); 95d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell if (pkStats != null) { 96d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell // Only count recency for apps that weren't the caller 97d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell // since the caller is always the most recent. 98d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell // Persistent processes muck this up, so omit them too. 99d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell if (!target.name.getPackageName().equals(mReferrerPackage) 100d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell && !isPersistentProcess(target)) { 101d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final long lastTimeUsed = pkStats.getLastTimeUsed(); 102d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell scoredTarget.lastTimeUsed = lastTimeUsed; 103d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell if (lastTimeUsed > mostRecentlyUsedTime) { 104d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell mostRecentlyUsedTime = lastTimeUsed; 105d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 106d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 107d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final long timeSpent = pkStats.getTotalTimeInForeground(); 108d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell scoredTarget.timeSpent = timeSpent; 109d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell if (timeSpent > mostTimeSpent) { 110d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell mostTimeSpent = timeSpent; 111d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 112d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final int launched = pkStats.mLaunchCount; 113d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell scoredTarget.launchCount = launched; 114d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell if (launched > mostLaunched) { 115d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell mostLaunched = launched; 116d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 117d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 118d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 119d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 120d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 121d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell if (DEBUG) { 122d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell Log.d(TAG, "compute - mostRecentlyUsedTime: " + mostRecentlyUsedTime 123d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell + " mostTimeSpent: " + mostTimeSpent 124d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell + " recentSinceTime: " + recentSinceTime 125d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell + " mostLaunched: " + mostLaunched); 126d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 127d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 128d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell for (ScoredTarget target : mScoredTargets.values()) { 129d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final float recency = (float) Math.max(target.lastTimeUsed - recentSinceTime, 0) 130d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell / (mostRecentlyUsedTime - recentSinceTime); 131d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final float recencyScore = recency * recency * RECENCY_MULTIPLIER; 132d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final float usageTimeScore = (float) target.timeSpent / mostTimeSpent; 133d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final float launchCountScore = (float) target.launchCount / mostLaunched; 134d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 135d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell target.score = recencyScore + usageTimeScore + launchCountScore; 136d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell if (DEBUG) { 137d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell Log.d(TAG, "Scores: recencyScore: " + recencyScore 138d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell + " usageTimeScore: " + usageTimeScore 139d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell + " launchCountScore: " + launchCountScore 140d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell + " - " + target); 141d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 142d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 143d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 144d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 145d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell static boolean isPersistentProcess(ResolvedComponentInfo rci) { 146d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell if (rci != null && rci.getCount() > 0) { 147d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell return (rci.getResolveInfoAt(0).activityInfo.applicationInfo.flags & 148d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell ApplicationInfo.FLAG_PERSISTENT) != 0; 149d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 150d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell return false; 151d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 152d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 153d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell @Override 154d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell public int compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp) { 155d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final ResolveInfo lhs = lhsp.getResolveInfoAt(0); 156d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final ResolveInfo rhs = rhsp.getResolveInfoAt(0); 157d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 158d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell // We want to put the one targeted to another user at the end of the dialog. 159d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell if (lhs.targetUserId != UserHandle.USER_CURRENT) { 160d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell return 1; 161d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 162d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 163d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell if (mHttp) { 164d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell // Special case: we want filters that match URI paths/schemes to be 165d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell // ordered before others. This is for the case when opening URIs, 166d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell // to make native apps go above browsers. 167d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final boolean lhsSpecific = ResolverActivity.isSpecificUriMatch(lhs.match); 168d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell final boolean rhsSpecific = ResolverActivity.isSpecificUriMatch(rhs.match); 169d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell if (lhsSpecific != rhsSpecific) { 170d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell return lhsSpecific ? -1 : 1; 171d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 172d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 173d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 1742388251fcd8def03a6bbc91382e84db085cf4253Adam Powell final boolean lPinned = lhsp.isPinned(); 1752388251fcd8def03a6bbc91382e84db085cf4253Adam Powell final boolean rPinned = rhsp.isPinned(); 176d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 1772388251fcd8def03a6bbc91382e84db085cf4253Adam Powell if (lPinned && !rPinned) { 1782388251fcd8def03a6bbc91382e84db085cf4253Adam Powell return -1; 1792388251fcd8def03a6bbc91382e84db085cf4253Adam Powell } else if (!lPinned && rPinned) { 1802388251fcd8def03a6bbc91382e84db085cf4253Adam Powell return 1; 1812388251fcd8def03a6bbc91382e84db085cf4253Adam Powell } 1822388251fcd8def03a6bbc91382e84db085cf4253Adam Powell 1832388251fcd8def03a6bbc91382e84db085cf4253Adam Powell // Pinned items stay stable within a normal lexical sort and ignore scoring. 1842388251fcd8def03a6bbc91382e84db085cf4253Adam Powell if (!lPinned && !rPinned) { 1852388251fcd8def03a6bbc91382e84db085cf4253Adam Powell if (mStats != null) { 1862388251fcd8def03a6bbc91382e84db085cf4253Adam Powell final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName( 1872388251fcd8def03a6bbc91382e84db085cf4253Adam Powell lhs.activityInfo.packageName, lhs.activityInfo.name)); 1882388251fcd8def03a6bbc91382e84db085cf4253Adam Powell final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName( 1892388251fcd8def03a6bbc91382e84db085cf4253Adam Powell rhs.activityInfo.packageName, rhs.activityInfo.name)); 1902388251fcd8def03a6bbc91382e84db085cf4253Adam Powell final float diff = rhsTarget.score - lhsTarget.score; 1912388251fcd8def03a6bbc91382e84db085cf4253Adam Powell 1922388251fcd8def03a6bbc91382e84db085cf4253Adam Powell if (diff != 0) { 1932388251fcd8def03a6bbc91382e84db085cf4253Adam Powell return diff > 0 ? 1 : -1; 1942388251fcd8def03a6bbc91382e84db085cf4253Adam Powell } 195d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 196d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 197d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 198d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell CharSequence sa = lhs.loadLabel(mPm); 199d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell if (sa == null) sa = lhs.activityInfo.name; 200d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell CharSequence sb = rhs.loadLabel(mPm); 201d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell if (sb == null) sb = rhs.activityInfo.name; 202d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 203d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell return mCollator.compare(sa.toString().trim(), sb.toString().trim()); 204d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 205d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 206a182e45c6851a8db89e8b0900f0812806ff295d4Adam Powell public float getScore(ComponentName name) { 207a182e45c6851a8db89e8b0900f0812806ff295d4Adam Powell final ScoredTarget target = mScoredTargets.get(name); 208a182e45c6851a8db89e8b0900f0812806ff295d4Adam Powell if (target != null) { 209a182e45c6851a8db89e8b0900f0812806ff295d4Adam Powell return target.score; 210a182e45c6851a8db89e8b0900f0812806ff295d4Adam Powell } 211a182e45c6851a8db89e8b0900f0812806ff295d4Adam Powell return 0; 212a182e45c6851a8db89e8b0900f0812806ff295d4Adam Powell } 213a182e45c6851a8db89e8b0900f0812806ff295d4Adam Powell 214d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell static class ScoredTarget { 215d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell public final ComponentInfo componentInfo; 216d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell public float score; 217d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell public long lastTimeUsed; 218d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell public long timeSpent; 219d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell public long launchCount; 220d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 221d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell public ScoredTarget(ComponentInfo ci) { 222d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell componentInfo = ci; 223d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 224d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell 225d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell @Override 226d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell public String toString() { 227d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell return "ScoredTarget{" + componentInfo 228d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell + " score: " + score 229d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell + " lastTimeUsed: " + lastTimeUsed 230d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell + " timeSpent: " + timeSpent 231d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell + " launchCount: " + launchCount 232d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell + "}"; 233d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 234d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell } 235d25267c0d81e84a064faf281a61c64eec3facf68Adam Powell} 236