1 /* 2 * Copyright (C) 2016 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.annotation.WorkerThread; 21 import android.app.ActivityManager; 22 import android.app.AppGlobals; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.ActivityInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.ResolveInfo; 30 import android.os.RemoteException; 31 import android.os.UserHandle; 32 import android.util.Log; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.app.chooser.DisplayResolveInfo; 36 import com.android.internal.app.chooser.TargetInfo; 37 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.PriorityQueue; 42 import java.util.concurrent.CountDownLatch; 43 44 /** 45 * A helper for the ResolverActivity that exposes methods to retrieve, filter and sort its list of 46 * resolvers. 47 */ 48 public class ResolverListController { 49 50 private final Context mContext; 51 private final PackageManager mpm; 52 private final int mLaunchedFromUid; 53 54 // Needed for sorting resolvers. 55 private final Intent mTargetIntent; 56 private final String mReferrerPackage; 57 58 private static final String TAG = "ResolverListController"; 59 private static final boolean DEBUG = false; 60 private final UserHandle mUserHandle; 61 62 private AbstractResolverComparator mResolverComparator; 63 private boolean isComputed = false; 64 private final UserHandle mQueryIntentsAsUser; 65 ResolverListController( Context context, PackageManager pm, Intent targetIntent, String referrerPackage, int launchedFromUid, UserHandle userHandle, UserHandle queryIntentsAsUser)66 public ResolverListController( 67 Context context, 68 PackageManager pm, 69 Intent targetIntent, 70 String referrerPackage, 71 int launchedFromUid, 72 UserHandle userHandle, 73 UserHandle queryIntentsAsUser) { 74 this(context, pm, targetIntent, referrerPackage, launchedFromUid, userHandle, 75 new ResolverRankerServiceResolverComparator( 76 context, 77 targetIntent, 78 referrerPackage, 79 null, 80 null, 81 userHandle), 82 queryIntentsAsUser); 83 } 84 ResolverListController( Context context, PackageManager pm, Intent targetIntent, String referrerPackage, int launchedFromUid, UserHandle userHandle, AbstractResolverComparator resolverComparator, UserHandle queryIntentsAsUser)85 public ResolverListController( 86 Context context, 87 PackageManager pm, 88 Intent targetIntent, 89 String referrerPackage, 90 int launchedFromUid, 91 UserHandle userHandle, 92 AbstractResolverComparator resolverComparator, 93 UserHandle queryIntentsAsUser) { 94 mContext = context; 95 mpm = pm; 96 mLaunchedFromUid = launchedFromUid; 97 mTargetIntent = targetIntent; 98 mReferrerPackage = referrerPackage; 99 mUserHandle = userHandle; 100 mResolverComparator = resolverComparator; 101 mQueryIntentsAsUser = queryIntentsAsUser; 102 } 103 104 @VisibleForTesting getLastChosen()105 public ResolveInfo getLastChosen() throws RemoteException { 106 return AppGlobals.getPackageManager().getLastChosenActivity( 107 mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()), 108 PackageManager.MATCH_DEFAULT_ONLY); 109 } 110 111 @VisibleForTesting setLastChosen(Intent intent, IntentFilter filter, int match)112 public void setLastChosen(Intent intent, IntentFilter filter, int match) 113 throws RemoteException { 114 AppGlobals.getPackageManager().setLastChosenActivity(intent, 115 intent.resolveType(mContext.getContentResolver()), 116 PackageManager.MATCH_DEFAULT_ONLY, 117 filter, match, intent.getComponent()); 118 } 119 120 @VisibleForTesting getResolversForIntent( boolean shouldGetResolvedFilter, boolean shouldGetActivityMetadata, boolean shouldGetOnlyDefaultActivities, List<Intent> intents)121 public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntent( 122 boolean shouldGetResolvedFilter, 123 boolean shouldGetActivityMetadata, 124 boolean shouldGetOnlyDefaultActivities, 125 List<Intent> intents) { 126 return getResolversForIntentAsUser(shouldGetResolvedFilter, shouldGetActivityMetadata, 127 shouldGetOnlyDefaultActivities, intents, mQueryIntentsAsUser); 128 } 129 getResolversForIntentAsUser( boolean shouldGetResolvedFilter, boolean shouldGetActivityMetadata, boolean shouldGetOnlyDefaultActivities, List<Intent> intents, UserHandle userHandle)130 public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntentAsUser( 131 boolean shouldGetResolvedFilter, 132 boolean shouldGetActivityMetadata, 133 boolean shouldGetOnlyDefaultActivities, 134 List<Intent> intents, 135 UserHandle userHandle) { 136 int baseFlags = (shouldGetOnlyDefaultActivities ? PackageManager.MATCH_DEFAULT_ONLY : 0) 137 | PackageManager.MATCH_DIRECT_BOOT_AWARE 138 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 139 | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) 140 | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0) 141 | PackageManager.MATCH_CLONE_PROFILE; 142 return getResolversForIntentAsUserInternal(intents, userHandle, baseFlags); 143 } 144 getResolversForIntentAsUserInternal( List<Intent> intents, UserHandle userHandle, int baseFlags)145 private List<ResolverActivity.ResolvedComponentInfo> getResolversForIntentAsUserInternal( 146 List<Intent> intents, 147 UserHandle userHandle, 148 int baseFlags) { 149 List<ResolverActivity.ResolvedComponentInfo> resolvedComponents = null; 150 for (int i = 0, N = intents.size(); i < N; i++) { 151 Intent intent = intents.get(i); 152 int flags = baseFlags; 153 if (intent.isWebIntent() 154 || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { 155 flags |= PackageManager.MATCH_INSTANT; 156 } 157 // Because of AIDL bug, queryIntentActivitiesAsUser can't accept subclasses of Intent. 158 intent = (intent.getClass() == Intent.class) ? intent : new Intent( 159 intent); 160 final List<ResolveInfo> infos = mpm.queryIntentActivitiesAsUser(intent, flags, 161 userHandle); 162 if (infos != null) { 163 if (resolvedComponents == null) { 164 resolvedComponents = new ArrayList<>(); 165 } 166 addResolveListDedupe(resolvedComponents, intent, infos); 167 } 168 } 169 return resolvedComponents; 170 } 171 172 @VisibleForTesting getUserHandle()173 public UserHandle getUserHandle() { 174 return mUserHandle; 175 } 176 177 @VisibleForTesting addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into, Intent intent, List<ResolveInfo> from)178 public void addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into, 179 Intent intent, 180 List<ResolveInfo> from) { 181 final int fromCount = from.size(); 182 final int intoCount = into.size(); 183 for (int i = 0; i < fromCount; i++) { 184 final ResolveInfo newInfo = from.get(i); 185 if (newInfo.userHandle == null) { 186 Log.w(TAG, "Skipping ResolveInfo with no userHandle: " + newInfo); 187 continue; 188 } 189 boolean found = false; 190 // Only loop to the end of into as it was before we started; no dupes in from. 191 for (int j = 0; j < intoCount; j++) { 192 final ResolverActivity.ResolvedComponentInfo rci = into.get(j); 193 if (isSameResolvedComponent(newInfo, rci)) { 194 found = true; 195 rci.add(intent, newInfo); 196 break; 197 } 198 } 199 if (!found) { 200 final ComponentName name = new ComponentName( 201 newInfo.activityInfo.packageName, newInfo.activityInfo.name); 202 final ResolverActivity.ResolvedComponentInfo rci = 203 new ResolverActivity.ResolvedComponentInfo(name, intent, newInfo); 204 rci.setPinned(isComponentPinned(name)); 205 rci.setFixedAtTop(isFixedAtTop(name)); 206 into.add(rci); 207 } 208 } 209 } 210 211 212 /** 213 * Whether this component is pinned by the user. Always false for resolver; overridden in 214 * Chooser. 215 */ isComponentPinned(ComponentName name)216 public boolean isComponentPinned(ComponentName name) { 217 return false; 218 } 219 220 /** 221 * Whether this component is fixed at top in the ranked apps list. Always false for Resolver; 222 * overridden in Chooser. 223 */ isFixedAtTop(ComponentName name)224 public boolean isFixedAtTop(ComponentName name) { 225 return false; 226 } 227 228 // Filter out any activities that the launched uid does not have permission for. 229 // To preserve the inputList, optionally will return the original list if any modification has 230 // been made. 231 @VisibleForTesting filterIneligibleActivities( List<ResolverActivity.ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified)232 public ArrayList<ResolverActivity.ResolvedComponentInfo> filterIneligibleActivities( 233 List<ResolverActivity.ResolvedComponentInfo> inputList, 234 boolean returnCopyOfOriginalListIfModified) { 235 ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null; 236 for (int i = inputList.size()-1; i >= 0; i--) { 237 ActivityInfo ai = inputList.get(i) 238 .getResolveInfoAt(0).activityInfo; 239 int granted = ActivityManager.checkComponentPermission( 240 ai.permission, mLaunchedFromUid, 241 ai.applicationInfo.uid, ai.exported); 242 243 if (granted != PackageManager.PERMISSION_GRANTED 244 || isComponentFiltered(ai.getComponentName())) { 245 // Access not allowed! We're about to filter an item, 246 // so modify the unfiltered version if it hasn't already been modified. 247 if (returnCopyOfOriginalListIfModified && listToReturn == null) { 248 listToReturn = new ArrayList<>(inputList); 249 } 250 inputList.remove(i); 251 } 252 } 253 return listToReturn; 254 } 255 256 // Filter out any low priority items. 257 // 258 // To preserve the inputList, optionally will return the original list if any modification has 259 // been made. 260 @VisibleForTesting filterLowPriority( List<ResolverActivity.ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified)261 public ArrayList<ResolverActivity.ResolvedComponentInfo> filterLowPriority( 262 List<ResolverActivity.ResolvedComponentInfo> inputList, 263 boolean returnCopyOfOriginalListIfModified) { 264 ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null; 265 // Only display the first matches that are either of equal 266 // priority or have asked to be default options. 267 ResolverActivity.ResolvedComponentInfo rci0 = inputList.get(0); 268 ResolveInfo r0 = rci0.getResolveInfoAt(0); 269 int N = inputList.size(); 270 for (int i = 1; i < N; i++) { 271 ResolveInfo ri = inputList.get(i).getResolveInfoAt(0); 272 if (DEBUG) Log.v( 273 TAG, 274 r0.activityInfo.name + "=" + 275 r0.priority + "/" + r0.isDefault + " vs " + 276 ri.activityInfo.name + "=" + 277 ri.priority + "/" + ri.isDefault); 278 if (r0.priority != ri.priority || 279 r0.isDefault != ri.isDefault) { 280 while (i < N) { 281 if (returnCopyOfOriginalListIfModified && listToReturn == null) { 282 listToReturn = new ArrayList<>(inputList); 283 } 284 inputList.remove(i); 285 N--; 286 } 287 } 288 } 289 return listToReturn; 290 } 291 292 private class ComputeCallback implements AbstractResolverComparator.AfterCompute { 293 294 private CountDownLatch mFinishComputeSignal; 295 ComputeCallback(CountDownLatch finishComputeSignal)296 public ComputeCallback(CountDownLatch finishComputeSignal) { 297 mFinishComputeSignal = finishComputeSignal; 298 } 299 afterCompute()300 public void afterCompute () { 301 mFinishComputeSignal.countDown(); 302 } 303 } 304 compute(List<ResolverActivity.ResolvedComponentInfo> inputList)305 private void compute(List<ResolverActivity.ResolvedComponentInfo> inputList) 306 throws InterruptedException { 307 if (mResolverComparator == null) { 308 Log.d(TAG, "Comparator has already been destroyed; skipped."); 309 return; 310 } 311 final CountDownLatch finishComputeSignal = new CountDownLatch(1); 312 ComputeCallback callback = new ComputeCallback(finishComputeSignal); 313 mResolverComparator.setCallBack(callback); 314 mResolverComparator.compute(inputList); 315 finishComputeSignal.await(); 316 isComputed = true; 317 } 318 319 @VisibleForTesting 320 @WorkerThread sort(List<ResolverActivity.ResolvedComponentInfo> inputList)321 public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) { 322 try { 323 long beforeRank = System.currentTimeMillis(); 324 if (!isComputed) { 325 compute(inputList); 326 } 327 Collections.sort(inputList, mResolverComparator); 328 329 long afterRank = System.currentTimeMillis(); 330 if (DEBUG) { 331 Log.d(TAG, "Time Cost: " + Long.toString(afterRank - beforeRank)); 332 } 333 } catch (InterruptedException e) { 334 Log.e(TAG, "Compute & Sort was interrupted: " + e); 335 } 336 } 337 338 @VisibleForTesting 339 @WorkerThread topK(List<ResolverActivity.ResolvedComponentInfo> inputList, int k)340 public void topK(List<ResolverActivity.ResolvedComponentInfo> inputList, int k) { 341 if (inputList == null || inputList.isEmpty() || k <= 0) { 342 return; 343 } 344 if (inputList.size() <= k) { 345 // Fall into normal sort when number of ranked elements 346 // needed is not smaller than size of input list. 347 sort(inputList); 348 return; 349 } 350 try { 351 long beforeRank = System.currentTimeMillis(); 352 if (!isComputed) { 353 compute(inputList); 354 } 355 356 // Top of this heap has lowest rank. 357 PriorityQueue<ResolverActivity.ResolvedComponentInfo> minHeap = new PriorityQueue<>(k, 358 (o1, o2) -> -mResolverComparator.compare(o1, o2)); 359 final int size = inputList.size(); 360 // Use this pointer to keep track of the position of next element 361 // to update in input list, starting from the last position. 362 int pointer = size - 1; 363 minHeap.addAll(inputList.subList(size - k, size)); 364 for (int i = size - k - 1; i >= 0; --i) { 365 ResolverActivity.ResolvedComponentInfo ci = inputList.get(i); 366 if (-mResolverComparator.compare(ci, minHeap.peek()) > 0) { 367 // When ranked higher than top of heap, remove top of heap, 368 // update input list with it, add this new element to heap. 369 inputList.set(pointer--, minHeap.poll()); 370 minHeap.add(ci); 371 } else { 372 // When ranked no higher than top of heap, update input list 373 // with this new element. 374 inputList.set(pointer--, ci); 375 } 376 } 377 378 // Now we have top k elements in heap, update first 379 // k positions of input list with them. 380 while (!minHeap.isEmpty()) { 381 inputList.set(pointer--, minHeap.poll()); 382 } 383 384 long afterRank = System.currentTimeMillis(); 385 if (DEBUG) { 386 Log.d(TAG, "Time Cost for top " + k + " targets: " 387 + Long.toString(afterRank - beforeRank)); 388 } 389 } catch (InterruptedException e) { 390 Log.e(TAG, "Compute & greatestOf was interrupted: " + e); 391 } 392 } 393 isSameResolvedComponent(ResolveInfo a, ResolverActivity.ResolvedComponentInfo b)394 private static boolean isSameResolvedComponent(ResolveInfo a, 395 ResolverActivity.ResolvedComponentInfo b) { 396 final ActivityInfo ai = a.activityInfo; 397 return ai.packageName.equals(b.name.getPackageName()) 398 && ai.name.equals(b.name.getClassName()); 399 } 400 isComponentFiltered(ComponentName componentName)401 boolean isComponentFiltered(ComponentName componentName) { 402 return false; 403 } 404 405 @VisibleForTesting getScore(DisplayResolveInfo target)406 public float getScore(DisplayResolveInfo target) { 407 return mResolverComparator.getScore(target); 408 } 409 410 /** 411 * Returns the app share score of the given {@code componentName}. 412 */ getScore(TargetInfo targetInfo)413 public float getScore(TargetInfo targetInfo) { 414 return mResolverComparator.getScore(targetInfo); 415 } 416 updateModel(TargetInfo targetInfo)417 public void updateModel(TargetInfo targetInfo) { 418 mResolverComparator.updateModel(targetInfo); 419 } 420 updateChooserCounts(String packageName, UserHandle user, String action)421 public void updateChooserCounts(String packageName, UserHandle user, String action) { 422 mResolverComparator.updateChooserCounts(packageName, user, action); 423 } 424 destroy()425 public void destroy() { 426 mResolverComparator.destroy(); 427 } 428 } 429