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 into.add(rci); 160 } 161 } 162 } 163 164 // Filter out any activities that the launched uid does not have permission for. 165 // To preserve the inputList, optionally will return the original list if any modification has 166 // been made. 167 @VisibleForTesting filterIneligibleActivities( List<ResolverActivity.ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified)168 public ArrayList<ResolverActivity.ResolvedComponentInfo> filterIneligibleActivities( 169 List<ResolverActivity.ResolvedComponentInfo> inputList, 170 boolean returnCopyOfOriginalListIfModified) { 171 ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null; 172 for (int i = inputList.size()-1; i >= 0; i--) { 173 ActivityInfo ai = inputList.get(i) 174 .getResolveInfoAt(0).activityInfo; 175 int granted = ActivityManager.checkComponentPermission( 176 ai.permission, mLaunchedFromUid, 177 ai.applicationInfo.uid, ai.exported); 178 179 if (granted != PackageManager.PERMISSION_GRANTED 180 || isComponentFiltered(ai.getComponentName())) { 181 // Access not allowed! We're about to filter an item, 182 // so modify the unfiltered version if it hasn't already been modified. 183 if (returnCopyOfOriginalListIfModified && listToReturn == null) { 184 listToReturn = new ArrayList<>(inputList); 185 } 186 inputList.remove(i); 187 } 188 } 189 return listToReturn; 190 } 191 192 // Filter out any low priority items. 193 // 194 // To preserve the inputList, optionally will return the original list if any modification has 195 // been made. 196 @VisibleForTesting filterLowPriority( List<ResolverActivity.ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified)197 public ArrayList<ResolverActivity.ResolvedComponentInfo> filterLowPriority( 198 List<ResolverActivity.ResolvedComponentInfo> inputList, 199 boolean returnCopyOfOriginalListIfModified) { 200 ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null; 201 // Only display the first matches that are either of equal 202 // priority or have asked to be default options. 203 ResolverActivity.ResolvedComponentInfo rci0 = inputList.get(0); 204 ResolveInfo r0 = rci0.getResolveInfoAt(0); 205 int N = inputList.size(); 206 for (int i = 1; i < N; i++) { 207 ResolveInfo ri = inputList.get(i).getResolveInfoAt(0); 208 if (DEBUG) Log.v( 209 TAG, 210 r0.activityInfo.name + "=" + 211 r0.priority + "/" + r0.isDefault + " vs " + 212 ri.activityInfo.name + "=" + 213 ri.priority + "/" + ri.isDefault); 214 if (r0.priority != ri.priority || 215 r0.isDefault != ri.isDefault) { 216 while (i < N) { 217 if (returnCopyOfOriginalListIfModified && listToReturn == null) { 218 listToReturn = new ArrayList<>(inputList); 219 } 220 inputList.remove(i); 221 N--; 222 } 223 } 224 } 225 return listToReturn; 226 } 227 228 private class ComputeCallback implements AbstractResolverComparator.AfterCompute { 229 230 private CountDownLatch mFinishComputeSignal; 231 ComputeCallback(CountDownLatch finishComputeSignal)232 public ComputeCallback(CountDownLatch finishComputeSignal) { 233 mFinishComputeSignal = finishComputeSignal; 234 } 235 afterCompute()236 public void afterCompute () { 237 mFinishComputeSignal.countDown(); 238 } 239 } 240 241 @VisibleForTesting 242 @WorkerThread sort(List<ResolverActivity.ResolvedComponentInfo> inputList)243 public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) { 244 if (mResolverComparator == null) { 245 Log.d(TAG, "Comparator has already been destroyed; skipped."); 246 return; 247 } 248 try { 249 long beforeRank = System.currentTimeMillis(); 250 if (!isComputed) { 251 final CountDownLatch finishComputeSignal = new CountDownLatch(1); 252 ComputeCallback callback = new ComputeCallback(finishComputeSignal); 253 mResolverComparator.setCallBack(callback); 254 mResolverComparator.compute(inputList); 255 finishComputeSignal.await(); 256 isComputed = true; 257 } 258 Collections.sort(inputList, mResolverComparator); 259 260 long afterRank = System.currentTimeMillis(); 261 if (DEBUG) { 262 Log.d(TAG, "Time Cost: " + Long.toString(afterRank - beforeRank)); 263 } 264 } catch (InterruptedException e) { 265 Log.e(TAG, "Compute & Sort was interrupted: " + e); 266 } 267 } 268 269 isSameResolvedComponent(ResolveInfo a, ResolverActivity.ResolvedComponentInfo b)270 private static boolean isSameResolvedComponent(ResolveInfo a, 271 ResolverActivity.ResolvedComponentInfo b) { 272 final ActivityInfo ai = a.activityInfo; 273 return ai.packageName.equals(b.name.getPackageName()) 274 && ai.name.equals(b.name.getClassName()); 275 } 276 isComponentFiltered(ComponentName componentName)277 boolean isComponentFiltered(ComponentName componentName) { 278 return false; 279 } 280 281 @VisibleForTesting getScore(ResolverActivity.DisplayResolveInfo target)282 public float getScore(ResolverActivity.DisplayResolveInfo target) { 283 return mResolverComparator.getScore(target.getResolvedComponentName()); 284 } 285 updateModel(ComponentName componentName)286 public void updateModel(ComponentName componentName) { 287 mResolverComparator.updateModel(componentName); 288 } 289 updateChooserCounts(String packageName, int userId, String action)290 public void updateChooserCounts(String packageName, int userId, String action) { 291 mResolverComparator.updateChooserCounts(packageName, userId, action); 292 } 293 destroy()294 public void destroy() { 295 mResolverComparator.destroy(); 296 } 297 } 298