Home | History | Annotate | Download | only in app
      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 {
    165         public void afterCompute ();
    166     }
    167 
    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.
    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 
    205     public void setCallBack(AfterCompute afterCompute) {
    206         mAfterCompute = afterCompute;
    207     }
    208 
    209     // compute features for each target according to usage stats of 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
    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 
    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 
    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.
    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.
    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.
    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.
    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.
    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.
    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 
    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
    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
    548         public void onServiceDisconnected(ComponentName name) {
    549             if (DEBUG) {
    550                 Log.d(TAG, "onServiceDisconnected: " + name);
    551             }
    552             synchronized (mLock) {
    553                 destroy();
    554             }
    555         }
    556 
    557         public void destroy() {
    558             synchronized (mLock) {
    559                 mRanker = null;
    560             }
    561         }
    562     }
    563 
    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.
    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.
    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
    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 
    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