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