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