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