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