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