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.PackageManager.NameNotFoundException;
30 import android.content.pm.ResolveInfo;
31 import android.content.SharedPreferences;
32 import android.content.ServiceConnection;
33 import android.metrics.LogMaker;
34 import android.os.Environment;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.Looper;
38 import android.os.Message;
39 import android.os.RemoteException;
40 import android.os.storage.StorageManager;
41 import android.os.UserHandle;
42 import android.service.resolver.IResolverRankerService;
43 import android.service.resolver.IResolverRankerResult;
44 import android.service.resolver.ResolverRankerService;
45 import android.service.resolver.ResolverTarget;
46 import android.text.TextUtils;
47 import android.util.ArrayMap;
48 import android.util.Log;
49 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
50 import com.android.internal.logging.MetricsLogger;
51 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
52 
53 import java.io.File;
54 import java.lang.InterruptedException;
55 import java.text.Collator;
56 import java.util.ArrayList;
57 import java.util.Comparator;
58 import java.util.concurrent.CountDownLatch;
59 import java.util.concurrent.TimeUnit;
60 import java.util.LinkedHashMap;
61 import java.util.List;
62 import java.util.Map;
63 
64 /**
65  * Ranks and compares packages based on usage stats.
66  */
67 class ResolverComparator implements Comparator<ResolvedComponentInfo> {
68     private static final String TAG = "ResolverComparator";
69 
70     private static final boolean DEBUG = false;
71 
72     private static final int NUM_OF_TOP_ANNOTATIONS_TO_USE = 3;
73 
74     // One week
75     private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 7;
76 
77     private static final long RECENCY_TIME_PERIOD = 1000 * 60 * 60 * 12;
78 
79     private static final float RECENCY_MULTIPLIER = 2.f;
80 
81     // message types
82     private static final int RESOLVER_RANKER_SERVICE_RESULT = 0;
83     private static final int RESOLVER_RANKER_RESULT_TIMEOUT = 1;
84 
85     // timeout for establishing connections with a ResolverRankerService.
86     private static final int CONNECTION_COST_TIMEOUT_MILLIS = 200;
87     // timeout for establishing connections with a ResolverRankerService, collecting features and
88     // predicting ranking scores.
89     private static final int WATCHDOG_TIMEOUT_MILLIS = 500;
90 
91     private final Collator mCollator;
92     private final boolean mHttp;
93     private final PackageManager mPm;
94     private final UsageStatsManager mUsm;
95     private final Map<String, UsageStats> mStats;
96     private final long mCurrentTime;
97     private final long mSinceTime;
98     private final LinkedHashMap<ComponentName, ResolverTarget> mTargetsDict = new LinkedHashMap<>();
99     private final String mReferrerPackage;
100     private final Object mLock = new Object();
101     private ArrayList<ResolverTarget> mTargets;
102     private String mContentType;
103     private String[] mAnnotations;
104     private String mAction;
105     private ComponentName mResolvedRankerName;
106     private ComponentName mRankerServiceName;
107     private IResolverRankerService mRanker;
108     private ResolverRankerServiceConnection mConnection;
109     private AfterCompute mAfterCompute;
110     private Context mContext;
111     private CountDownLatch mConnectSignal;
112 
113     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
114         public void handleMessage(Message msg) {
115             switch (msg.what) {
116                 case RESOLVER_RANKER_SERVICE_RESULT:
117                     if (DEBUG) {
118                         Log.d(TAG, "RESOLVER_RANKER_SERVICE_RESULT");
119                     }
120                     if (mHandler.hasMessages(RESOLVER_RANKER_RESULT_TIMEOUT)) {
121                         if (msg.obj != null) {
122                             final List<ResolverTarget> receivedTargets =
123                                     (List<ResolverTarget>) msg.obj;
124                             if (receivedTargets != null && mTargets != null
125                                     && receivedTargets.size() == mTargets.size()) {
126                                 final int size = mTargets.size();
127                                 boolean isUpdated = false;
128                                 for (int i = 0; i < size; ++i) {
129                                     final float predictedProb =
130                                             receivedTargets.get(i).getSelectProbability();
131                                     if (predictedProb != mTargets.get(i).getSelectProbability()) {
132                                         mTargets.get(i).setSelectProbability(predictedProb);
133                                         isUpdated = true;
134                                     }
135                                 }
136                                 if (isUpdated) {
137                                     mRankerServiceName = mResolvedRankerName;
138                                 }
139                             } else {
140                                 Log.e(TAG, "Sizes of sent and received ResolverTargets diff.");
141                             }
142                         } else {
143                             Log.e(TAG, "Receiving null prediction results.");
144                         }
145                         mHandler.removeMessages(RESOLVER_RANKER_RESULT_TIMEOUT);
146                         mAfterCompute.afterCompute();
147                     }
148                     break;
149 
150                 case RESOLVER_RANKER_RESULT_TIMEOUT:
151                     if (DEBUG) {
152                         Log.d(TAG, "RESOLVER_RANKER_RESULT_TIMEOUT; unbinding services");
153                     }
154                     mHandler.removeMessages(RESOLVER_RANKER_SERVICE_RESULT);
155                     mAfterCompute.afterCompute();
156                     break;
157 
158                 default:
159                     super.handleMessage(msg);
160             }
161         }
162     };
163 
164     public interface AfterCompute {
afterCompute()165         public void afterCompute ();
166     }
167 
ResolverComparator(Context context, Intent intent, String referrerPackage, AfterCompute afterCompute)168     public ResolverComparator(Context context, Intent intent, String referrerPackage,
169                               AfterCompute afterCompute) {
170         mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
171         String scheme = intent.getScheme();
172         mHttp = "http".equals(scheme) || "https".equals(scheme);
173         mReferrerPackage = referrerPackage;
174         mAfterCompute = afterCompute;
175         mContext = context;
176 
177         mPm = context.getPackageManager();
178         mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
179 
180         mCurrentTime = System.currentTimeMillis();
181         mSinceTime = mCurrentTime - USAGE_STATS_PERIOD;
182         mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime);
183         mContentType = intent.getType();
184         getContentAnnotations(intent);
185         mAction = intent.getAction();
186         mRankerServiceName = new ComponentName(mContext, this.getClass());
187     }
188 
189     // get annotations of content from intent.
getContentAnnotations(Intent intent)190     public void getContentAnnotations(Intent intent) {
191         ArrayList<String> annotations = intent.getStringArrayListExtra(
192                 Intent.EXTRA_CONTENT_ANNOTATIONS);
193         if (annotations != null) {
194             int size = annotations.size();
195             if (size > NUM_OF_TOP_ANNOTATIONS_TO_USE) {
196                 size = NUM_OF_TOP_ANNOTATIONS_TO_USE;
197             }
198             mAnnotations = new String[size];
199             for (int i = 0; i < size; i++) {
200                 mAnnotations[i] = annotations.get(i);
201             }
202         }
203     }
204 
setCallBack(AfterCompute afterCompute)205     public void setCallBack(AfterCompute afterCompute) {
206         mAfterCompute = afterCompute;
207     }
208 
209     // compute features for each target according to usage stats of targets.
compute(List<ResolvedComponentInfo> targets)210     public void compute(List<ResolvedComponentInfo> targets) {
211         reset();
212 
213         final long recentSinceTime = mCurrentTime - RECENCY_TIME_PERIOD;
214 
215         float mostRecencyScore = 1.0f;
216         float mostTimeSpentScore = 1.0f;
217         float mostLaunchScore = 1.0f;
218         float mostChooserScore = 1.0f;
219 
220         for (ResolvedComponentInfo target : targets) {
221             final ResolverTarget resolverTarget = new ResolverTarget();
222             mTargetsDict.put(target.name, resolverTarget);
223             final UsageStats pkStats = mStats.get(target.name.getPackageName());
224             if (pkStats != null) {
225                 // Only count recency for apps that weren't the caller
226                 // since the caller is always the most recent.
227                 // Persistent processes muck this up, so omit them too.
228                 if (!target.name.getPackageName().equals(mReferrerPackage)
229                         && !isPersistentProcess(target)) {
230                     final float recencyScore =
231                             (float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0);
232                     resolverTarget.setRecencyScore(recencyScore);
233                     if (recencyScore > mostRecencyScore) {
234                         mostRecencyScore = recencyScore;
235                     }
236                 }
237                 final float timeSpentScore = (float) pkStats.getTotalTimeInForeground();
238                 resolverTarget.setTimeSpentScore(timeSpentScore);
239                 if (timeSpentScore > mostTimeSpentScore) {
240                     mostTimeSpentScore = timeSpentScore;
241                 }
242                 final float launchScore = (float) pkStats.mLaunchCount;
243                 resolverTarget.setLaunchScore(launchScore);
244                 if (launchScore > mostLaunchScore) {
245                     mostLaunchScore = launchScore;
246                 }
247 
248                 float chooserScore = 0.0f;
249                 if (pkStats.mChooserCounts != null && mAction != null
250                         && pkStats.mChooserCounts.get(mAction) != null) {
251                     chooserScore = (float) pkStats.mChooserCounts.get(mAction)
252                             .getOrDefault(mContentType, 0);
253                     if (mAnnotations != null) {
254                         final int size = mAnnotations.length;
255                         for (int i = 0; i < size; i++) {
256                             chooserScore += (float) pkStats.mChooserCounts.get(mAction)
257                                     .getOrDefault(mAnnotations[i], 0);
258                         }
259                     }
260                 }
261                 if (DEBUG) {
262                     if (mAction == null) {
263                         Log.d(TAG, "Action type is null");
264                     } else {
265                         Log.d(TAG, "Chooser Count of " + mAction + ":" +
266                                 target.name.getPackageName() + " is " +
267                                 Float.toString(chooserScore));
268                     }
269                 }
270                 resolverTarget.setChooserScore(chooserScore);
271                 if (chooserScore > mostChooserScore) {
272                     mostChooserScore = chooserScore;
273                 }
274             }
275         }
276 
277         if (DEBUG) {
278             Log.d(TAG, "compute - mostRecencyScore: " + mostRecencyScore
279                     + " mostTimeSpentScore: " + mostTimeSpentScore
280                     + " mostLaunchScore: " + mostLaunchScore
281                     + " mostChooserScore: " + mostChooserScore);
282         }
283 
284         mTargets = new ArrayList<>(mTargetsDict.values());
285         for (ResolverTarget target : mTargets) {
286             final float recency = target.getRecencyScore() / mostRecencyScore;
287             setFeatures(target, recency * recency * RECENCY_MULTIPLIER,
288                     target.getLaunchScore() / mostLaunchScore,
289                     target.getTimeSpentScore() / mostTimeSpentScore,
290                     target.getChooserScore() / mostChooserScore);
291             addDefaultSelectProbability(target);
292             if (DEBUG) {
293                 Log.d(TAG, "Scores: " + target);
294             }
295         }
296         predictSelectProbabilities(mTargets);
297     }
298 
299     @Override
compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp)300     public int compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp) {
301         final ResolveInfo lhs = lhsp.getResolveInfoAt(0);
302         final ResolveInfo rhs = rhsp.getResolveInfoAt(0);
303 
304         // We want to put the one targeted to another user at the end of the dialog.
305         if (lhs.targetUserId != UserHandle.USER_CURRENT) {
306             return rhs.targetUserId != UserHandle.USER_CURRENT ? 0 : 1;
307         }
308         if (rhs.targetUserId != UserHandle.USER_CURRENT) {
309             return -1;
310         }
311 
312         if (mHttp) {
313             // Special case: we want filters that match URI paths/schemes to be
314             // ordered before others.  This is for the case when opening URIs,
315             // to make native apps go above browsers.
316             final boolean lhsSpecific = ResolverActivity.isSpecificUriMatch(lhs.match);
317             final boolean rhsSpecific = ResolverActivity.isSpecificUriMatch(rhs.match);
318             if (lhsSpecific != rhsSpecific) {
319                 return lhsSpecific ? -1 : 1;
320             }
321         }
322 
323         final boolean lPinned = lhsp.isPinned();
324         final boolean rPinned = rhsp.isPinned();
325 
326         if (lPinned && !rPinned) {
327             return -1;
328         } else if (!lPinned && rPinned) {
329             return 1;
330         }
331 
332         // Pinned items stay stable within a normal lexical sort and ignore scoring.
333         if (!lPinned && !rPinned) {
334             if (mStats != null) {
335                 final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName(
336                         lhs.activityInfo.packageName, lhs.activityInfo.name));
337                 final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName(
338                         rhs.activityInfo.packageName, rhs.activityInfo.name));
339 
340                 if (lhsTarget != null && rhsTarget != null) {
341                     final int selectProbabilityDiff = Float.compare(
342                         rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
343 
344                     if (selectProbabilityDiff != 0) {
345                         return selectProbabilityDiff > 0 ? 1 : -1;
346                     }
347                 }
348             }
349         }
350 
351         CharSequence  sa = lhs.loadLabel(mPm);
352         if (sa == null) sa = lhs.activityInfo.name;
353         CharSequence  sb = rhs.loadLabel(mPm);
354         if (sb == null) sb = rhs.activityInfo.name;
355 
356         return mCollator.compare(sa.toString().trim(), sb.toString().trim());
357     }
358 
getScore(ComponentName name)359     public float getScore(ComponentName name) {
360         final ResolverTarget target = mTargetsDict.get(name);
361         if (target != null) {
362             return target.getSelectProbability();
363         }
364         return 0;
365     }
366 
updateChooserCounts(String packageName, int userId, String action)367     public void updateChooserCounts(String packageName, int userId, String action) {
368         if (mUsm != null) {
369             mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
370         }
371     }
372 
373     // update ranking model when the connection to it is valid.
updateModel(ComponentName componentName)374     public void updateModel(ComponentName componentName) {
375         synchronized (mLock) {
376             if (mRanker != null) {
377                 try {
378                     int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet())
379                             .indexOf(componentName);
380                     if (selectedPos >= 0 && mTargets != null) {
381                         final float selectedProbability = getScore(componentName);
382                         int order = 0;
383                         for (ResolverTarget target : mTargets) {
384                             if (target.getSelectProbability() > selectedProbability) {
385                                 order++;
386                             }
387                         }
388                         logMetrics(order);
389                         mRanker.train(mTargets, selectedPos);
390                     } else {
391                         if (DEBUG) {
392                             Log.d(TAG, "Selected a unknown component: " + componentName);
393                         }
394                     }
395                 } catch (RemoteException e) {
396                     Log.e(TAG, "Error in Train: " + e);
397                 }
398             } else {
399                 if (DEBUG) {
400                     Log.d(TAG, "Ranker is null; skip updateModel.");
401                 }
402             }
403         }
404     }
405 
406     // unbind the service and clear unhandled messges.
destroy()407     public void destroy() {
408         mHandler.removeMessages(RESOLVER_RANKER_SERVICE_RESULT);
409         mHandler.removeMessages(RESOLVER_RANKER_RESULT_TIMEOUT);
410         if (mConnection != null) {
411             mContext.unbindService(mConnection);
412             mConnection.destroy();
413         }
414         if (mAfterCompute != null) {
415             mAfterCompute.afterCompute();
416         }
417         if (DEBUG) {
418             Log.d(TAG, "Unbinded Resolver Ranker.");
419         }
420     }
421 
422     // records metrics for evaluation.
logMetrics(int selectedPos)423     private void logMetrics(int selectedPos) {
424         if (mRankerServiceName != null) {
425             MetricsLogger metricsLogger = new MetricsLogger();
426             LogMaker log = new LogMaker(MetricsEvent.ACTION_TARGET_SELECTED);
427             log.setComponentName(mRankerServiceName);
428             int isCategoryUsed = (mAnnotations == null) ? 0 : 1;
429             log.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, isCategoryUsed);
430             log.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION, selectedPos);
431             metricsLogger.write(log);
432         }
433     }
434 
435     // connect to a ranking service.
initRanker(Context context)436     private void initRanker(Context context) {
437         synchronized (mLock) {
438             if (mConnection != null && mRanker != null) {
439                 if (DEBUG) {
440                     Log.d(TAG, "Ranker still exists; reusing the existing one.");
441                 }
442                 return;
443             }
444         }
445         Intent intent = resolveRankerService();
446         if (intent == null) {
447             return;
448         }
449         mConnectSignal = new CountDownLatch(1);
450         mConnection = new ResolverRankerServiceConnection(mConnectSignal);
451         context.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
452     }
453 
454     // resolve the service for ranking.
resolveRankerService()455     private Intent resolveRankerService() {
456         Intent intent = new Intent(ResolverRankerService.SERVICE_INTERFACE);
457         final List<ResolveInfo> resolveInfos = mPm.queryIntentServices(intent, 0);
458         for (ResolveInfo resolveInfo : resolveInfos) {
459             if (resolveInfo == null || resolveInfo.serviceInfo == null
460                     || resolveInfo.serviceInfo.applicationInfo == null) {
461                 if (DEBUG) {
462                     Log.d(TAG, "Failed to retrieve a ranker: " + resolveInfo);
463                 }
464                 continue;
465             }
466             ComponentName componentName = new ComponentName(
467                     resolveInfo.serviceInfo.applicationInfo.packageName,
468                     resolveInfo.serviceInfo.name);
469             try {
470                 final String perm = mPm.getServiceInfo(componentName, 0).permission;
471                 if (!ResolverRankerService.BIND_PERMISSION.equals(perm)) {
472                     Log.w(TAG, "ResolverRankerService " + componentName + " does not require"
473                             + " permission " + ResolverRankerService.BIND_PERMISSION
474                             + " - this service will not be queried for ResolverComparator."
475                             + " add android:permission=\""
476                             + ResolverRankerService.BIND_PERMISSION + "\""
477                             + " to the <service> tag for " + componentName
478                             + " in the manifest.");
479                     continue;
480                 }
481                 if (PackageManager.PERMISSION_GRANTED != mPm.checkPermission(
482                         ResolverRankerService.HOLD_PERMISSION,
483                         resolveInfo.serviceInfo.packageName)) {
484                     Log.w(TAG, "ResolverRankerService " + componentName + " does not hold"
485                             + " permission " + ResolverRankerService.HOLD_PERMISSION
486                             + " - this service will not be queried for ResolverComparator.");
487                     continue;
488                 }
489             } catch (NameNotFoundException e) {
490                 Log.e(TAG, "Could not look up service " + componentName
491                         + "; component name not found");
492                 continue;
493             }
494             if (DEBUG) {
495                 Log.d(TAG, "Succeeded to retrieve a ranker: " + componentName);
496             }
497             mResolvedRankerName = componentName;
498             intent.setComponent(componentName);
499             return intent;
500         }
501         return null;
502     }
503 
504     // set a watchdog, to avoid waiting for ranking service for too long.
startWatchDog(int timeOutLimit)505     private void startWatchDog(int timeOutLimit) {
506         if (DEBUG) Log.d(TAG, "Setting watchdog timer for " + timeOutLimit + "ms");
507         if (mHandler == null) {
508             Log.d(TAG, "Error: Handler is Null; Needs to be initialized.");
509         }
510         mHandler.sendEmptyMessageDelayed(RESOLVER_RANKER_RESULT_TIMEOUT, timeOutLimit);
511     }
512 
513     private class ResolverRankerServiceConnection implements ServiceConnection {
514         private final CountDownLatch mConnectSignal;
515 
ResolverRankerServiceConnection(CountDownLatch connectSignal)516         public ResolverRankerServiceConnection(CountDownLatch connectSignal) {
517             mConnectSignal = connectSignal;
518         }
519 
520         public final IResolverRankerResult resolverRankerResult =
521                 new IResolverRankerResult.Stub() {
522             @Override
523             public void sendResult(List<ResolverTarget> targets) throws RemoteException {
524                 if (DEBUG) {
525                     Log.d(TAG, "Sending Result back to Resolver: " + targets);
526                 }
527                 synchronized (mLock) {
528                     final Message msg = Message.obtain();
529                     msg.what = RESOLVER_RANKER_SERVICE_RESULT;
530                     msg.obj = targets;
531                     mHandler.sendMessage(msg);
532                 }
533             }
534         };
535 
536         @Override
onServiceConnected(ComponentName name, IBinder service)537         public void onServiceConnected(ComponentName name, IBinder service) {
538             if (DEBUG) {
539                 Log.d(TAG, "onServiceConnected: " + name);
540             }
541             synchronized (mLock) {
542                 mRanker = IResolverRankerService.Stub.asInterface(service);
543                 mConnectSignal.countDown();
544             }
545         }
546 
547         @Override
onServiceDisconnected(ComponentName name)548         public void onServiceDisconnected(ComponentName name) {
549             if (DEBUG) {
550                 Log.d(TAG, "onServiceDisconnected: " + name);
551             }
552             synchronized (mLock) {
553                 destroy();
554             }
555         }
556 
destroy()557         public void destroy() {
558             synchronized (mLock) {
559                 mRanker = null;
560             }
561         }
562     }
563 
reset()564     private void reset() {
565         mTargetsDict.clear();
566         mTargets = null;
567         mRankerServiceName = new ComponentName(mContext, this.getClass());
568         mResolvedRankerName = null;
569         startWatchDog(WATCHDOG_TIMEOUT_MILLIS);
570         initRanker(mContext);
571     }
572 
573     // predict select probabilities if ranking service is valid.
predictSelectProbabilities(List<ResolverTarget> targets)574     private void predictSelectProbabilities(List<ResolverTarget> targets) {
575         if (mConnection == null) {
576             if (DEBUG) {
577                 Log.d(TAG, "Has not found valid ResolverRankerService; Skip Prediction");
578             }
579         } else {
580             try {
581                 mConnectSignal.await(CONNECTION_COST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
582                 synchronized (mLock) {
583                     if (mRanker != null) {
584                         mRanker.predict(targets, mConnection.resolverRankerResult);
585                         return;
586                     } else {
587                         if (DEBUG) {
588                             Log.d(TAG, "Ranker has not been initialized; skip predict.");
589                         }
590                     }
591                 }
592             } catch (InterruptedException e) {
593                 Log.e(TAG, "Error in Wait for Service Connection.");
594             } catch (RemoteException e) {
595                 Log.e(TAG, "Error in Predict: " + e);
596             }
597         }
598         if (mAfterCompute != null) {
599             mAfterCompute.afterCompute();
600         }
601     }
602 
603     // adds select prob as the default values, according to a pre-trained Logistic Regression model.
addDefaultSelectProbability(ResolverTarget target)604     private void addDefaultSelectProbability(ResolverTarget target) {
605         float sum = 2.5543f * target.getLaunchScore() + 2.8412f * target.getTimeSpentScore() +
606                 0.269f * target.getRecencyScore() + 4.2222f * target.getChooserScore();
607         target.setSelectProbability((float) (1.0 / (1.0 + Math.exp(1.6568f - sum))));
608     }
609 
610     // sets features for each target
setFeatures(ResolverTarget target, float recencyScore, float launchScore, float timeSpentScore, float chooserScore)611     private void setFeatures(ResolverTarget target, float recencyScore, float launchScore,
612                              float timeSpentScore, float chooserScore) {
613         target.setRecencyScore(recencyScore);
614         target.setLaunchScore(launchScore);
615         target.setTimeSpentScore(timeSpentScore);
616         target.setChooserScore(chooserScore);
617     }
618 
isPersistentProcess(ResolvedComponentInfo rci)619     static boolean isPersistentProcess(ResolvedComponentInfo rci) {
620         if (rci != null && rci.getCount() > 0) {
621             return (rci.getResolveInfoAt(0).activityInfo.applicationInfo.flags &
622                     ApplicationInfo.FLAG_PERSISTENT) != 0;
623         }
624         return false;
625     }
626 }
627