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 Object mLock = new Object(); 60 61 @GuardedBy("mLock") 62 private ResolverComparator mResolverComparator; 63 private boolean isComputed = false; 64 65 public ResolverListController( 66 Context context, 67 PackageManager pm, 68 Intent targetIntent, 69 String referrerPackage, 70 int launchedFromUid) { 71 mContext = context; 72 mpm = pm; 73 mLaunchedFromUid = launchedFromUid; 74 mTargetIntent = targetIntent; 75 mReferrerPackage = referrerPackage; 76 synchronized (mLock) { 77 mResolverComparator = 78 new ResolverComparator(mContext, mTargetIntent, mReferrerPackage, null); 79 } 80 } 81 82 @VisibleForTesting 83 public ResolveInfo getLastChosen() throws RemoteException { 84 return AppGlobals.getPackageManager().getLastChosenActivity( 85 mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()), 86 PackageManager.MATCH_DEFAULT_ONLY); 87 } 88 89 @VisibleForTesting 90 public void setLastChosen(Intent intent, IntentFilter filter, int match) 91 throws RemoteException { 92 AppGlobals.getPackageManager().setLastChosenActivity(intent, 93 intent.resolveType(mContext.getContentResolver()), 94 PackageManager.MATCH_DEFAULT_ONLY, 95 filter, match, intent.getComponent()); 96 } 97 98 @VisibleForTesting 99 public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntent( 100 boolean shouldGetResolvedFilter, 101 boolean shouldGetActivityMetadata, 102 List<Intent> intents) { 103 List<ResolverActivity.ResolvedComponentInfo> resolvedComponents = null; 104 for (int i = 0, N = intents.size(); i < N; i++) { 105 final Intent intent = intents.get(i); 106 final List<ResolveInfo> infos = mpm.queryIntentActivities(intent, 107 PackageManager.MATCH_DEFAULT_ONLY 108 | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) 109 | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0) 110 | PackageManager.MATCH_INSTANT); 111 // Remove any activities that are not exported. 112 int totalSize = infos.size(); 113 for (int j = totalSize - 1; j >= 0 ; j--) { 114 ResolveInfo info = infos.get(j); 115 if (info.activityInfo != null && !info.activityInfo.exported) { 116 infos.remove(j); 117 } 118 } 119 if (infos != null) { 120 if (resolvedComponents == null) { 121 resolvedComponents = new ArrayList<>(); 122 } 123 addResolveListDedupe(resolvedComponents, intent, infos); 124 } 125 } 126 return resolvedComponents; 127 } 128 129 @VisibleForTesting 130 public void addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into, 131 Intent intent, 132 List<ResolveInfo> from) { 133 final int fromCount = from.size(); 134 final int intoCount = into.size(); 135 for (int i = 0; i < fromCount; i++) { 136 final ResolveInfo newInfo = from.get(i); 137 boolean found = false; 138 // Only loop to the end of into as it was before we started; no dupes in from. 139 for (int j = 0; j < intoCount; j++) { 140 final ResolverActivity.ResolvedComponentInfo rci = into.get(j); 141 if (isSameResolvedComponent(newInfo, rci)) { 142 found = true; 143 rci.add(intent, newInfo); 144 break; 145 } 146 } 147 if (!found) { 148 final ComponentName name = new ComponentName( 149 newInfo.activityInfo.packageName, newInfo.activityInfo.name); 150 final ResolverActivity.ResolvedComponentInfo rci = 151 new ResolverActivity.ResolvedComponentInfo(name, intent, newInfo); 152 rci.setPinned(isComponentPinned(name)); 153 into.add(rci); 154 } 155 } 156 } 157 158 // Filter out any activities that the launched uid does not have permission for. 159 // 160 // Also filter out those that are suspended because they couldn't be started. We don't do this 161 // when we have an explicit list of resolved activities, because that only happens when 162 // we are being subclassed, so we can safely launch whatever they gave us. 163 // 164 // To preserve the inputList, optionally will return the original list if any modification has 165 // been made. 166 @VisibleForTesting 167 public ArrayList<ResolverActivity.ResolvedComponentInfo> filterIneligibleActivities( 168 List<ResolverActivity.ResolvedComponentInfo> inputList, 169 boolean returnCopyOfOriginalListIfModified) { 170 ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null; 171 for (int i = inputList.size()-1; i >= 0; i--) { 172 ActivityInfo ai = inputList.get(i) 173 .getResolveInfoAt(0).activityInfo; 174 int granted = ActivityManager.checkComponentPermission( 175 ai.permission, mLaunchedFromUid, 176 ai.applicationInfo.uid, ai.exported); 177 boolean suspended = (ai.applicationInfo.flags 178 & ApplicationInfo.FLAG_SUSPENDED) != 0; 179 if (granted != PackageManager.PERMISSION_GRANTED || suspended 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 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 ResolverComparator.AfterCompute { 229 230 private CountDownLatch mFinishComputeSignal; 231 232 public ComputeCallback(CountDownLatch finishComputeSignal) { 233 mFinishComputeSignal = finishComputeSignal; 234 } 235 236 public void afterCompute () { 237 mFinishComputeSignal.countDown(); 238 } 239 } 240 241 @VisibleForTesting 242 @WorkerThread 243 public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) { 244 synchronized (mLock) { 245 if (mResolverComparator == null) { 246 Log.d(TAG, "Comparator has already been destroyed; skipped."); 247 return; 248 } 249 final CountDownLatch finishComputeSignal = new CountDownLatch(1); 250 ComputeCallback callback = new ComputeCallback(finishComputeSignal); 251 mResolverComparator.setCallBack(callback); 252 try { 253 long beforeRank = System.currentTimeMillis(); 254 if (!isComputed) { 255 mResolverComparator.compute(inputList); 256 finishComputeSignal.await(); 257 isComputed = true; 258 } 259 Collections.sort(inputList, mResolverComparator); 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 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 277 boolean isComponentPinned(ComponentName name) { 278 return false; 279 } 280 281 boolean isComponentFiltered(ComponentName componentName) { 282 return false; 283 } 284 285 @VisibleForTesting 286 public float getScore(ResolverActivity.DisplayResolveInfo target) { 287 synchronized (mLock) { 288 if (mResolverComparator == null) { 289 return 0.0f; 290 } 291 return mResolverComparator.getScore(target.getResolvedComponentName()); 292 } 293 } 294 295 public void updateModel(ComponentName componentName) { 296 synchronized (mLock) { 297 if (mResolverComparator != null) { 298 mResolverComparator.updateModel(componentName); 299 } 300 } 301 } 302 303 public void updateChooserCounts(String packageName, int userId, String action) { 304 synchronized (mLock) { 305 if (mResolverComparator != null) { 306 mResolverComparator.updateChooserCounts(packageName, userId, action); 307 } 308 } 309 } 310 311 public void destroy() { 312 synchronized (mLock) { 313 if (mResolverComparator != null) { 314 mResolverComparator.destroy(); 315 } 316 mResolverComparator = null; 317 } 318 } 319 } 320