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