• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.PackageManager;
29 import android.content.pm.ResolveInfo;
30 import android.os.RemoteException;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.concurrent.CountDownLatch;
39 
40 /**
41  * A helper for the ResolverActivity that exposes methods to retrieve, filter and sort its list of
42  * resolvers.
43  */
44 public class ResolverListController {
45 
46     private final Context mContext;
47     private final PackageManager mpm;
48     private final int mLaunchedFromUid;
49 
50     // Needed for sorting resolvers.
51     private final Intent mTargetIntent;
52     private final String mReferrerPackage;
53 
54     private static final String TAG = "ResolverListController";
55     private static final boolean DEBUG = false;
56 
57     private AbstractResolverComparator mResolverComparator;
58     private boolean isComputed = false;
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         this(context, pm, targetIntent, referrerPackage, launchedFromUid,
67                     new ResolverRankerServiceResolverComparator(
68                         context, targetIntent, referrerPackage, null));
69     }
70 
ResolverListController( Context context, PackageManager pm, Intent targetIntent, String referrerPackage, int launchedFromUid, AbstractResolverComparator resolverComparator)71     public ResolverListController(
72             Context context,
73             PackageManager pm,
74             Intent targetIntent,
75             String referrerPackage,
76             int launchedFromUid,
77             AbstractResolverComparator resolverComparator) {
78         mContext = context;
79         mpm = pm;
80         mLaunchedFromUid = launchedFromUid;
81         mTargetIntent = targetIntent;
82         mReferrerPackage = referrerPackage;
83         mResolverComparator = resolverComparator;
84     }
85 
86     @VisibleForTesting
getLastChosen()87     public ResolveInfo getLastChosen() throws RemoteException {
88         return AppGlobals.getPackageManager().getLastChosenActivity(
89                 mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
90                 PackageManager.MATCH_DEFAULT_ONLY);
91     }
92 
93     @VisibleForTesting
setLastChosen(Intent intent, IntentFilter filter, int match)94     public void setLastChosen(Intent intent, IntentFilter filter, int match)
95             throws RemoteException {
96         AppGlobals.getPackageManager().setLastChosenActivity(intent,
97                 intent.resolveType(mContext.getContentResolver()),
98                 PackageManager.MATCH_DEFAULT_ONLY,
99                 filter, match, intent.getComponent());
100     }
101 
102     @VisibleForTesting
getResolversForIntent( boolean shouldGetResolvedFilter, boolean shouldGetActivityMetadata, List<Intent> intents)103     public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntent(
104             boolean shouldGetResolvedFilter,
105             boolean shouldGetActivityMetadata,
106             List<Intent> intents) {
107         List<ResolverActivity.ResolvedComponentInfo> resolvedComponents = null;
108         for (int i = 0, N = intents.size(); i < N; i++) {
109             final Intent intent = intents.get(i);
110             int flags = PackageManager.MATCH_DEFAULT_ONLY
111                     | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
112                     | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0);
113             if (intent.isWebIntent()
114                         || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
115                 flags |= PackageManager.MATCH_INSTANT;
116             }
117             final List<ResolveInfo> infos = mpm.queryIntentActivities(intent, flags);
118             // Remove any activities that are not exported.
119             int totalSize = infos.size();
120             for (int j = totalSize - 1; j >= 0 ; j--) {
121                 ResolveInfo info = infos.get(j);
122                 if (info.activityInfo != null && !info.activityInfo.exported) {
123                     infos.remove(j);
124                 }
125             }
126             if (infos != null) {
127                 if (resolvedComponents == null) {
128                     resolvedComponents = new ArrayList<>();
129                 }
130                 addResolveListDedupe(resolvedComponents, intent, infos);
131             }
132         }
133         return resolvedComponents;
134     }
135 
136     @VisibleForTesting
addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into, Intent intent, List<ResolveInfo> from)137     public void addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into,
138             Intent intent,
139             List<ResolveInfo> from) {
140         final int fromCount = from.size();
141         final int intoCount = into.size();
142         for (int i = 0; i < fromCount; i++) {
143             final ResolveInfo newInfo = from.get(i);
144             boolean found = false;
145             // Only loop to the end of into as it was before we started; no dupes in from.
146             for (int j = 0; j < intoCount; j++) {
147                 final ResolverActivity.ResolvedComponentInfo rci = into.get(j);
148                 if (isSameResolvedComponent(newInfo, rci)) {
149                     found = true;
150                     rci.add(intent, newInfo);
151                     break;
152                 }
153             }
154             if (!found) {
155                 final ComponentName name = new ComponentName(
156                         newInfo.activityInfo.packageName, newInfo.activityInfo.name);
157                 final ResolverActivity.ResolvedComponentInfo rci =
158                         new ResolverActivity.ResolvedComponentInfo(name, intent, newInfo);
159                 into.add(rci);
160             }
161         }
162     }
163 
164     // Filter out any activities that the launched uid does not have permission for.
165     // To preserve the inputList, optionally will return the original list if any modification has
166     // been made.
167     @VisibleForTesting
filterIneligibleActivities( List<ResolverActivity.ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified)168     public ArrayList<ResolverActivity.ResolvedComponentInfo> filterIneligibleActivities(
169             List<ResolverActivity.ResolvedComponentInfo> inputList,
170             boolean returnCopyOfOriginalListIfModified) {
171         ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null;
172         for (int i = inputList.size()-1; i >= 0; i--) {
173             ActivityInfo ai = inputList.get(i)
174                     .getResolveInfoAt(0).activityInfo;
175             int granted = ActivityManager.checkComponentPermission(
176                     ai.permission, mLaunchedFromUid,
177                     ai.applicationInfo.uid, ai.exported);
178 
179             if (granted != PackageManager.PERMISSION_GRANTED
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
filterLowPriority( List<ResolverActivity.ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified)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 AbstractResolverComparator.AfterCompute {
229 
230         private CountDownLatch mFinishComputeSignal;
231 
ComputeCallback(CountDownLatch finishComputeSignal)232         public ComputeCallback(CountDownLatch finishComputeSignal) {
233             mFinishComputeSignal = finishComputeSignal;
234         }
235 
afterCompute()236         public void afterCompute () {
237             mFinishComputeSignal.countDown();
238         }
239     }
240 
241     @VisibleForTesting
242     @WorkerThread
sort(List<ResolverActivity.ResolvedComponentInfo> inputList)243     public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) {
244         if (mResolverComparator == null) {
245             Log.d(TAG, "Comparator has already been destroyed; skipped.");
246             return;
247         }
248         try {
249             long beforeRank = System.currentTimeMillis();
250             if (!isComputed) {
251                 final CountDownLatch finishComputeSignal = new CountDownLatch(1);
252                 ComputeCallback callback = new ComputeCallback(finishComputeSignal);
253                 mResolverComparator.setCallBack(callback);
254                 mResolverComparator.compute(inputList);
255                 finishComputeSignal.await();
256                 isComputed = true;
257             }
258             Collections.sort(inputList, mResolverComparator);
259 
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 
isSameResolvedComponent(ResolveInfo a, ResolverActivity.ResolvedComponentInfo b)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 
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