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