2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 package com
.android
.internal
.app
;
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
;
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
;
43 * Ranks and compares packages based on usage stats.
45 class ResolverComparator
implements Comparator
<ResolvedComponentInfo
> {
46 private static final String TAG
= "ResolverComparator";
48 private static final boolean DEBUG
= false;
51 private static final long USAGE_STATS_PERIOD
= 1000 * 60 * 60 * 24 * 14;
53 private static final long RECENCY_TIME_PERIOD
= 1000 * 60 * 60 * 12;
55 private static final float RECENCY_MULTIPLIER
= 3.f
;
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
;
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
;
73 mPm
= context
.getPackageManager();
74 mUsm
= (UsageStatsManager
) context
.getSystemService(Context
.USAGE_STATS_SERVICE
);
76 mCurrentTime
= System
.currentTimeMillis();
77 mSinceTime
= mCurrentTime
- USAGE_STATS_PERIOD
;
78 mStats
= mUsm
.queryAndAggregateUsageStats(mSinceTime
, mCurrentTime
);
81 public void compute(List
<ResolvedComponentInfo
> targets
) {
82 mScoredTargets
.clear();
84 final long recentSinceTime
= mCurrentTime
- RECENCY_TIME_PERIOD
;
86 long mostRecentlyUsedTime
= recentSinceTime
+ 1;
87 long mostTimeSpent
= 1;
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
;
107 final long timeSpent
= pkStats
.getTotalTimeInForeground();
108 scoredTarget
.timeSpent
= timeSpent
;
109 if (timeSpent
> mostTimeSpent
) {
110 mostTimeSpent
= timeSpent
;
112 final int launched
= pkStats
.mLaunchCount
;
113 scoredTarget
.launchCount
= launched
;
114 if (launched
> mostLaunched
) {
115 mostLaunched
= launched
;
122 Log
.d(TAG
, "compute - mostRecentlyUsedTime: " + mostRecentlyUsedTime
123 + " mostTimeSpent: " + mostTimeSpent
124 + " recentSinceTime: " + recentSinceTime
125 + " mostLaunched: " + mostLaunched
);
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
;
135 target
.score
= recencyScore
+ usageTimeScore
+ launchCountScore
;
137 Log
.d(TAG
, "Scores: recencyScore: " + recencyScore
138 + " usageTimeScore: " + usageTimeScore
139 + " launchCountScore: " + launchCountScore
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;
154 public int compare(ResolvedComponentInfo lhsp
, ResolvedComponentInfo rhsp
) {
155 final ResolveInfo lhs
= lhsp
.getResolveInfoAt(0);
156 final ResolveInfo rhs
= rhsp
.getResolveInfoAt(0);
158 // We want to put the one targeted to another user at the end of the dialog.
159 if (lhs
.targetUserId
!= UserHandle
.USER_CURRENT
) {
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;
174 if (mStats
!= null) {
175 final ScoredTarget lhsTarget
= mScoredTargets
.get(new ComponentName(
176 lhs
.activityInfo
.packageName
, lhs
.activityInfo
.name
));
177 final ScoredTarget rhsTarget
= mScoredTargets
.get(new ComponentName(
178 rhs
.activityInfo
.packageName
, rhs
.activityInfo
.name
));
179 final float diff
= rhsTarget
.score
- lhsTarget
.score
;
182 return diff
> 0 ?
1 : -1;
186 CharSequence sa
= lhs
.loadLabel(mPm
);
187 if (sa
== null) sa
= lhs
.activityInfo
.name
;
188 CharSequence sb
= rhs
.loadLabel(mPm
);
189 if (sb
== null) sb
= rhs
.activityInfo
.name
;
191 return mCollator
.compare(sa
.toString().trim(), sb
.toString().trim());
194 public float getScore(ComponentName name
) {
195 final ScoredTarget target
= mScoredTargets
.get(name
);
196 if (target
!= null) {
202 static class ScoredTarget
{
203 public final ComponentInfo componentInfo
;
205 public long lastTimeUsed
;
206 public long timeSpent
;
207 public long launchCount
;
209 public ScoredTarget(ComponentInfo ci
) {
214 public String
toString() {
215 return "ScoredTarget{" + componentInfo
217 + " lastTimeUsed: " + lastTimeUsed
218 + " timeSpent: " + timeSpent
219 + " launchCount: " + launchCount