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.util.Log; 32 33 import com.android.internal.annotations.VisibleForTesting; 34 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.List; 38 import java.util.concurrent.CountDownLatch; 39 40 /** 41 * A helper for the ResolverActivity that exposes methods to retrieve, filter and sort its list of 42 * resolvers. 43 */ 44 public class ResolverListController { 45 46 private final Context mContext; 47 private final PackageManager mpm; 48 private final int mLaunchedFromUid; 49 50 // Needed for sorting resolvers. 51 private final Intent mTargetIntent; 52 private final String mReferrerPackage; 53 54 private static final String TAG = "ResolverListController"; 55 private static final boolean DEBUG = false; 56 57 private AbstractResolverComparator mResolverComparator; 58 private boolean isComputed = false; 59 ResolverListController( Context context, PackageManager pm, Intent targetIntent, String referrerPackage, int launchedFromUid)60 public ResolverListController( 61 Context context, 62 PackageManager pm, 63 Intent targetIntent, 64 String referrerPackage, 65 int launchedFromUid) { 66 this(context, pm, targetIntent, referrerPackage, launchedFromUid, 67 new ResolverRankerServiceResolverComparator( 68 context, targetIntent, referrerPackage, null)); 69 } 70 ResolverListController( Context context, PackageManager pm, Intent targetIntent, String referrerPackage, int launchedFromUid, AbstractResolverComparator resolverComparator)71 public ResolverListController( 72 Context context, 73 PackageManager pm, 74 Intent targetIntent, 75 String referrerPackage, 76 int launchedFromUid, 77 AbstractResolverComparator resolverComparator) { 78 mContext = context; 79 mpm = pm; 80 mLaunchedFromUid = launchedFromUid; 81 mTargetIntent = targetIntent; 82 mReferrerPackage = referrerPackage; 83 mResolverComparator = resolverComparator; 84 } 85 86 @VisibleForTesting getLastChosen()87 public ResolveInfo getLastChosen() throws RemoteException { 88 return AppGlobals.getPackageManager().getLastChosenActivity( 89 mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()), 90 PackageManager.MATCH_DEFAULT_ONLY); 91 } 92 93 @VisibleForTesting setLastChosen(Intent intent, IntentFilter filter, int match)94 public void setLastChosen(Intent intent, IntentFilter filter, int match) 95 throws RemoteException { 96 AppGlobals.getPackageManager().setLastChosenActivity(intent, 97 intent.resolveType(mContext.getContentResolver()), 98 PackageManager.MATCH_DEFAULT_ONLY, 99 filter, match, intent.getComponent()); 100 } 101 102 @VisibleForTesting getResolversForIntent( boolean shouldGetResolvedFilter, boolean shouldGetActivityMetadata, List<Intent> intents)103 public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntent( 104 boolean shouldGetResolvedFilter, 105 boolean shouldGetActivityMetadata, 106 List<Intent> intents) { 107 List<ResolverActivity.ResolvedComponentInfo> resolvedComponents = null; 108 for (int i = 0, N = intents.size(); i < N; i++) { 109 final Intent intent = intents.get(i); 110 int flags = PackageManager.MATCH_DEFAULT_ONLY 111 | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) 112 | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0); 113 if (intent.isWebIntent() 114 || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { 115 flags |= PackageManager.MATCH_INSTANT; 116 } 117 final List<ResolveInfo> infos = mpm.queryIntentActivities(intent, flags); 118 // Remove any activities that are not exported. 119 int totalSize = infos.size(); 120 for (int j = totalSize - 1; j >= 0 ; j--) { 121 ResolveInfo info = infos.get(j); 122 if (info.activityInfo != null && !info.activityInfo.exported) { 123 infos.remove(j); 124 } 125 } 126 if (infos != null) { 127 if (resolvedComponents == null) { 128 resolvedComponents = new ArrayList<>(); 129 } 130 addResolveListDedupe(resolvedComponents, intent, infos); 131 } 132 } 133 return resolvedComponents; 134 } 135 136 @VisibleForTesting addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into, Intent intent, List<ResolveInfo> from)137 public void addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into, 138 Intent intent, 139 List<ResolveInfo> from) { 140 final int fromCount = from.size(); 141 final int intoCount = into.size(); 142 for (int i = 0; i < fromCount; i++) { 143 final ResolveInfo newInfo = from.get(i); 144 boolean found = false; 145 // Only loop to the end of into as it was before we started; no dupes in from. 146 for (int j = 0; j < intoCount; j++) { 147 final ResolverActivity.ResolvedComponentInfo rci = into.get(j); 148 if (isSameResolvedComponent(newInfo, rci)) { 149 found = true; 150 rci.add(intent, newInfo); 151 break; 152 } 153 } 154 if (!found) { 155 final ComponentName name = new ComponentName( 156 newInfo.activityInfo.packageName, newInfo.activityInfo.name); 157 final ResolverActivity.ResolvedComponentInfo rci = 158 new ResolverActivity.ResolvedComponentInfo(name, intent, newInfo); 159 rci.setPinned(isComponentPinned(name)); 160 into.add(rci); 161 } 162 } 163 } 164 165 166 /** 167 * Whether this component is pinned by the user. Always false for resolver; overridden in 168 * Chooser. 169 */ isComponentPinned(ComponentName name)170 public boolean isComponentPinned(ComponentName name) { 171 return false; 172 } 173 174 175 // Filter out any activities that the launched uid does not have permission for. 176 // To preserve the inputList, optionally will return the original list if any modification has 177 // been made. 178 @VisibleForTesting filterIneligibleActivities( List<ResolverActivity.ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified)179 public ArrayList<ResolverActivity.ResolvedComponentInfo> filterIneligibleActivities( 180 List<ResolverActivity.ResolvedComponentInfo> inputList, 181 boolean returnCopyOfOriginalListIfModified) { 182 ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null; 183 for (int i = inputList.size()-1; i >= 0; i--) { 184 ActivityInfo ai = inputList.get(i) 185 .getResolveInfoAt(0).activityInfo; 186 int granted = ActivityManager.checkComponentPermission( 187 ai.permission, mLaunchedFromUid, 188 ai.applicationInfo.uid, ai.exported); 189 190 if (granted != PackageManager.PERMISSION_GRANTED 191 || isComponentFiltered(ai.getComponentName())) { 192 // Access not allowed! We're about to filter an item, 193 // so modify the unfiltered version if it hasn't already been modified. 194 if (returnCopyOfOriginalListIfModified && listToReturn == null) { 195 listToReturn = new ArrayList<>(inputList); 196 } 197 inputList.remove(i); 198 } 199 } 200 return listToReturn; 201 } 202 203 // Filter out any low priority items. 204 // 205 // To preserve the inputList, optionally will return the original list if any modification has 206 // been made. 207 @VisibleForTesting filterLowPriority( List<ResolverActivity.ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified)208 public ArrayList<ResolverActivity.ResolvedComponentInfo> filterLowPriority( 209 List<ResolverActivity.ResolvedComponentInfo> inputList, 210 boolean returnCopyOfOriginalListIfModified) { 211 ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null; 212 // Only display the first matches that are either of equal 213 // priority or have asked to be default options. 214 ResolverActivity.ResolvedComponentInfo rci0 = inputList.get(0); 215 ResolveInfo r0 = rci0.getResolveInfoAt(0); 216 int N = inputList.size(); 217 for (int i = 1; i < N; i++) { 218 ResolveInfo ri = inputList.get(i).getResolveInfoAt(0); 219 if (DEBUG) Log.v( 220 TAG, 221 r0.activityInfo.name + "=" + 222 r0.priority + "/" + r0.isDefault + " vs " + 223 ri.activityInfo.name + "=" + 224 ri.priority + "/" + ri.isDefault); 225 if (r0.priority != ri.priority || 226 r0.isDefault != ri.isDefault) { 227 while (i < N) { 228 if (returnCopyOfOriginalListIfModified && listToReturn == null) { 229 listToReturn = new ArrayList<>(inputList); 230 } 231 inputList.remove(i); 232 N--; 233 } 234 } 235 } 236 return listToReturn; 237 } 238 239 private class ComputeCallback implements AbstractResolverComparator.AfterCompute { 240 241 private CountDownLatch mFinishComputeSignal; 242 ComputeCallback(CountDownLatch finishComputeSignal)243 public ComputeCallback(CountDownLatch finishComputeSignal) { 244 mFinishComputeSignal = finishComputeSignal; 245 } 246 afterCompute()247 public void afterCompute () { 248 mFinishComputeSignal.countDown(); 249 } 250 } 251 252 @VisibleForTesting 253 @WorkerThread sort(List<ResolverActivity.ResolvedComponentInfo> inputList)254 public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) { 255 if (mResolverComparator == null) { 256 Log.d(TAG, "Comparator has already been destroyed; skipped."); 257 return; 258 } 259 try { 260 long beforeRank = System.currentTimeMillis(); 261 if (!isComputed) { 262 final CountDownLatch finishComputeSignal = new CountDownLatch(1); 263 ComputeCallback callback = new ComputeCallback(finishComputeSignal); 264 mResolverComparator.setCallBack(callback); 265 mResolverComparator.compute(inputList); 266 finishComputeSignal.await(); 267 isComputed = true; 268 } 269 Collections.sort(inputList, mResolverComparator); 270 271 long afterRank = System.currentTimeMillis(); 272 if (DEBUG) { 273 Log.d(TAG, "Time Cost: " + Long.toString(afterRank - beforeRank)); 274 } 275 } catch (InterruptedException e) { 276 Log.e(TAG, "Compute & Sort was interrupted: " + e); 277 } 278 } 279 280 isSameResolvedComponent(ResolveInfo a, ResolverActivity.ResolvedComponentInfo b)281 private static boolean isSameResolvedComponent(ResolveInfo a, 282 ResolverActivity.ResolvedComponentInfo b) { 283 final ActivityInfo ai = a.activityInfo; 284 return ai.packageName.equals(b.name.getPackageName()) 285 && ai.name.equals(b.name.getClassName()); 286 } 287 isComponentFiltered(ComponentName componentName)288 boolean isComponentFiltered(ComponentName componentName) { 289 return false; 290 } 291 292 @VisibleForTesting getScore(ResolverActivity.DisplayResolveInfo target)293 public float getScore(ResolverActivity.DisplayResolveInfo target) { 294 return mResolverComparator.getScore(target.getResolvedComponentName()); 295 } 296 updateModel(ComponentName componentName)297 public void updateModel(ComponentName componentName) { 298 mResolverComparator.updateModel(componentName); 299 } 300 updateChooserCounts(String packageName, int userId, String action)301 public void updateChooserCounts(String packageName, int userId, String action) { 302 mResolverComparator.updateChooserCounts(packageName, userId, action); 303 } 304 destroy()305 public void destroy() { 306 mResolverComparator.destroy(); 307 } 308 } 309