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