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