• 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.intentresolver;
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.os.UserHandle;
32 import android.util.Log;
33 
34 import com.android.intentresolver.chooser.DisplayResolveInfo;
35 import com.android.intentresolver.chooser.TargetInfo;
36 import com.android.intentresolver.model.AbstractResolverComparator;
37 import com.android.internal.annotations.VisibleForTesting;
38 
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.List;
42 import java.util.PriorityQueue;
43 import java.util.concurrent.CountDownLatch;
44 
45 /**
46  * A helper for the ResolverActivity that exposes methods to retrieve, filter and sort its list of
47  * resolvers.
48  */
49 public class ResolverListController {
50 
51     private final Context mContext;
52     private final PackageManager mpm;
53     private final int mLaunchedFromUid;
54 
55     // Needed for sorting resolvers.
56     private final Intent mTargetIntent;
57     private final String mReferrerPackage;
58 
59     private static final String TAG = "ResolverListController";
60     private static final boolean DEBUG = false;
61     private final UserHandle mQueryIntentsAsUser;
62 
63     private AbstractResolverComparator mResolverComparator;
64     private boolean isComputed = false;
65 
ResolverListController( Context context, PackageManager pm, Intent targetIntent, String referrerPackage, int launchedFromUid, AbstractResolverComparator resolverComparator, UserHandle queryIntentsAsUser)66     public ResolverListController(
67             Context context,
68             PackageManager pm,
69             Intent targetIntent,
70             String referrerPackage,
71             int launchedFromUid,
72             AbstractResolverComparator resolverComparator,
73             UserHandle queryIntentsAsUser) {
74         mContext = context;
75         mpm = pm;
76         mLaunchedFromUid = launchedFromUid;
77         mTargetIntent = targetIntent;
78         mReferrerPackage = referrerPackage;
79         mResolverComparator = resolverComparator;
80         mQueryIntentsAsUser = queryIntentsAsUser;
81     }
82 
83     @VisibleForTesting
getLastChosen()84     public ResolveInfo getLastChosen() throws RemoteException {
85         return AppGlobals.getPackageManager().getLastChosenActivity(
86                 mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
87                 PackageManager.MATCH_DEFAULT_ONLY);
88     }
89 
90     @VisibleForTesting
setLastChosen(Intent intent, IntentFilter filter, int match)91     public void setLastChosen(Intent intent, IntentFilter filter, int match)
92             throws RemoteException {
93         AppGlobals.getPackageManager().setLastChosenActivity(intent,
94                 intent.resolveType(mContext.getContentResolver()),
95                 PackageManager.MATCH_DEFAULT_ONLY,
96                 filter, match, intent.getComponent());
97     }
98 
99     /**
100      * Get data about all the ways the user with the specified handle can resolve any of the
101      * provided {@code intents}.
102      */
getResolversForIntentAsUser( boolean shouldGetResolvedFilter, boolean shouldGetActivityMetadata, boolean shouldGetOnlyDefaultActivities, List<Intent> intents, UserHandle userHandle)103     public List<ResolvedComponentInfo> getResolversForIntentAsUser(
104             boolean shouldGetResolvedFilter,
105             boolean shouldGetActivityMetadata,
106             boolean shouldGetOnlyDefaultActivities,
107             List<Intent> intents,
108             UserHandle userHandle) {
109         int baseFlags = (shouldGetOnlyDefaultActivities ? PackageManager.MATCH_DEFAULT_ONLY : 0)
110                 | PackageManager.MATCH_DIRECT_BOOT_AWARE
111                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
112                 | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
113                 | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0)
114                 | PackageManager.MATCH_CLONE_PROFILE;
115         return getResolversForIntentAsUserInternal(intents, userHandle, baseFlags);
116     }
117 
getResolversForIntentAsUserInternal( List<Intent> intents, UserHandle userHandle, int baseFlags)118     private List<ResolvedComponentInfo> getResolversForIntentAsUserInternal(
119             List<Intent> intents, UserHandle userHandle, int baseFlags) {
120         List<ResolvedComponentInfo> resolvedComponents = null;
121         for (int i = 0, N = intents.size(); i < N; i++) {
122             Intent intent = intents.get(i);
123             int flags = baseFlags;
124             if (intent.isWebIntent()
125                         || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
126                 flags |= PackageManager.MATCH_INSTANT;
127             }
128             // Because of AIDL bug, queryIntentActivitiesAsUser can't accept subclasses of Intent.
129             intent = (intent.getClass() == Intent.class) ? intent : new Intent(
130                     intent);
131             final List<ResolveInfo> infos = mpm.queryIntentActivitiesAsUser(intent, flags,
132                     userHandle);
133             if (infos != null) {
134                 if (resolvedComponents == null) {
135                     resolvedComponents = new ArrayList<>();
136                 }
137                 addResolveListDedupe(resolvedComponents, intent, infos);
138             }
139         }
140         return resolvedComponents;
141     }
142 
143     @VisibleForTesting
addResolveListDedupe( List<ResolvedComponentInfo> into, Intent intent, List<ResolveInfo> from)144     public void addResolveListDedupe(
145             List<ResolvedComponentInfo> into, Intent intent, List<ResolveInfo> from) {
146         final int fromCount = from.size();
147         final int intoCount = into.size();
148         for (int i = 0; i < fromCount; i++) {
149             final ResolveInfo newInfo = from.get(i);
150             if (newInfo.userHandle == null) {
151                 Log.w(TAG, "Skipping ResolveInfo with no userHandle: " + newInfo);
152                 continue;
153             }
154             boolean found = false;
155             // Only loop to the end of into as it was before we started; no dupes in from.
156             for (int j = 0; j < intoCount; j++) {
157                 final ResolvedComponentInfo rci = into.get(j);
158                 if (isSameResolvedComponent(newInfo, rci)) {
159                     found = true;
160                     rci.add(intent, newInfo);
161                     break;
162                 }
163             }
164             if (!found) {
165                 final ComponentName name = new ComponentName(
166                         newInfo.activityInfo.packageName, newInfo.activityInfo.name);
167                 final ResolvedComponentInfo rci = new ResolvedComponentInfo(name, intent, newInfo);
168                 rci.setPinned(isComponentPinned(name));
169                 into.add(rci);
170             }
171         }
172     }
173 
174 
175     /**
176      * Whether this component is pinned by the user. Always false for resolver; overridden in
177      * Chooser.
178      */
isComponentPinned(ComponentName name)179     public boolean isComponentPinned(ComponentName name) {
180         return false;
181     }
182 
183     // Filter out any activities that the launched uid does not have permission for.
184     // To preserve the inputList, optionally will return the original list if any modification has
185     // been made.
186     @VisibleForTesting
filterIneligibleActivities( List<ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified)187     public ArrayList<ResolvedComponentInfo> filterIneligibleActivities(
188             List<ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified) {
189         ArrayList<ResolvedComponentInfo> listToReturn = null;
190         for (int i = inputList.size()-1; i >= 0; i--) {
191             ActivityInfo ai = inputList.get(i)
192                     .getResolveInfoAt(0).activityInfo;
193             int granted = ActivityManager.checkComponentPermission(
194                     ai.permission, mLaunchedFromUid,
195                     ai.applicationInfo.uid, ai.exported);
196 
197             if (granted != PackageManager.PERMISSION_GRANTED
198                     || isComponentFiltered(ai.getComponentName())) {
199                 // Access not allowed! We're about to filter an item,
200                 // so modify the unfiltered version if it hasn't already been modified.
201                 if (returnCopyOfOriginalListIfModified && listToReturn == null) {
202                     listToReturn = new ArrayList<>(inputList);
203                 }
204                 inputList.remove(i);
205             }
206         }
207         return listToReturn;
208     }
209 
210     // Filter out any low priority items.
211     //
212     // To preserve the inputList, optionally will return the original list if any modification has
213     // been made.
214     @VisibleForTesting
filterLowPriority( List<ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified)215     public ArrayList<ResolvedComponentInfo> filterLowPriority(
216             List<ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified) {
217         ArrayList<ResolvedComponentInfo> listToReturn = null;
218         // Only display the first matches that are either of equal
219         // priority or have asked to be default options.
220         ResolvedComponentInfo rci0 = inputList.get(0);
221         ResolveInfo r0 = rci0.getResolveInfoAt(0);
222         int N = inputList.size();
223         for (int i = 1; i < N; i++) {
224             ResolveInfo ri = inputList.get(i).getResolveInfoAt(0);
225             if (DEBUG) Log.v(
226                     TAG,
227                     r0.activityInfo.name + "=" +
228                             r0.priority + "/" + r0.isDefault + " vs " +
229                             ri.activityInfo.name + "=" +
230                             ri.priority + "/" + ri.isDefault);
231             if (r0.priority != ri.priority ||
232                     r0.isDefault != ri.isDefault) {
233                 while (i < N) {
234                     if (returnCopyOfOriginalListIfModified && listToReturn == null) {
235                         listToReturn = new ArrayList<>(inputList);
236                     }
237                     inputList.remove(i);
238                     N--;
239                 }
240             }
241         }
242         return listToReturn;
243     }
244 
compute(List<ResolvedComponentInfo> inputList)245     private void compute(List<ResolvedComponentInfo> inputList) throws InterruptedException {
246         if (mResolverComparator == null) {
247             Log.d(TAG, "Comparator has already been destroyed; skipped.");
248             return;
249         }
250         final CountDownLatch finishComputeSignal = new CountDownLatch(1);
251         mResolverComparator.setCallBack(() -> finishComputeSignal.countDown());
252         mResolverComparator.compute(inputList);
253         finishComputeSignal.await();
254         isComputed = true;
255     }
256 
257     @VisibleForTesting
258     @WorkerThread
sort(List<ResolvedComponentInfo> inputList)259     public void sort(List<ResolvedComponentInfo> inputList) {
260         try {
261             long beforeRank = System.currentTimeMillis();
262             if (!isComputed) {
263                 compute(inputList);
264             }
265             Collections.sort(inputList, mResolverComparator);
266 
267             long afterRank = System.currentTimeMillis();
268             if (DEBUG) {
269                 Log.d(TAG, "Time Cost: " + Long.toString(afterRank - beforeRank));
270             }
271         } catch (InterruptedException e) {
272             Log.e(TAG, "Compute & Sort was interrupted: " + e);
273         }
274     }
275 
276     @VisibleForTesting
277     @WorkerThread
topK(List<ResolvedComponentInfo> inputList, int k)278     public void topK(List<ResolvedComponentInfo> inputList, int k) {
279         if (inputList == null || inputList.isEmpty() || k <= 0) {
280             return;
281         }
282         if (inputList.size() <= k) {
283             // Fall into normal sort when number of ranked elements
284             // needed is not smaller than size of input list.
285             sort(inputList);
286             return;
287         }
288         try {
289             long beforeRank = System.currentTimeMillis();
290             if (!isComputed) {
291                 compute(inputList);
292             }
293 
294             // Top of this heap has lowest rank.
295             PriorityQueue<ResolvedComponentInfo> minHeap = new PriorityQueue<>(k,
296                     (o1, o2) -> -mResolverComparator.compare(o1, o2));
297             final int size = inputList.size();
298             // Use this pointer to keep track of the position of next element
299             // to update in input list, starting from the last position.
300             int pointer = size - 1;
301             minHeap.addAll(inputList.subList(size - k, size));
302             for (int i = size - k - 1; i >= 0; --i) {
303                 ResolvedComponentInfo ci = inputList.get(i);
304                 if (-mResolverComparator.compare(ci, minHeap.peek()) > 0) {
305                     // When ranked higher than top of heap, remove top of heap,
306                     // update input list with it, add this new element to heap.
307                     inputList.set(pointer--, minHeap.poll());
308                     minHeap.add(ci);
309                 } else {
310                     // When ranked no higher than top of heap, update input list
311                     // with this new element.
312                     inputList.set(pointer--, ci);
313                 }
314             }
315 
316             // Now we have top k elements in heap, update first
317             // k positions of input list with them.
318             while (!minHeap.isEmpty()) {
319                 inputList.set(pointer--, minHeap.poll());
320             }
321 
322             long afterRank = System.currentTimeMillis();
323             if (DEBUG) {
324                 Log.d(TAG, "Time Cost for top " + k + " targets: "
325                         + Long.toString(afterRank - beforeRank));
326             }
327         } catch (InterruptedException e) {
328             Log.e(TAG, "Compute & greatestOf was interrupted: " + e);
329         }
330     }
331 
isSameResolvedComponent(ResolveInfo a, ResolvedComponentInfo b)332     private static boolean isSameResolvedComponent(ResolveInfo a, ResolvedComponentInfo b) {
333         final ActivityInfo ai = a.activityInfo;
334         return ai.packageName.equals(b.name.getPackageName())
335                 && ai.name.equals(b.name.getClassName());
336     }
337 
isComponentFiltered(ComponentName componentName)338     boolean isComponentFiltered(ComponentName componentName) {
339         return false;
340     }
341 
342     @VisibleForTesting
getScore(DisplayResolveInfo target)343     public float getScore(DisplayResolveInfo target) {
344         return mResolverComparator.getScore(target);
345     }
346 
347     /**
348      * Returns the app share score of the given {@code componentName}.
349      */
getScore(TargetInfo targetInfo)350     public float getScore(TargetInfo targetInfo) {
351         return mResolverComparator.getScore(targetInfo);
352     }
353 
354     /**
355      * Updates the model about the chosen {@code targetInfo}.
356      */
updateModel(TargetInfo targetInfo)357     public void updateModel(TargetInfo targetInfo) {
358         mResolverComparator.updateModel(targetInfo);
359     }
360 
361     /**
362      * Updates the model about Chooser Activity selection.
363      */
updateChooserCounts(String packageName, UserHandle user, String action)364     public void updateChooserCounts(String packageName, UserHandle user, String action) {
365         mResolverComparator.updateChooserCounts(packageName, user, action);
366     }
367 
destroy()368     public void destroy() {
369         mResolverComparator.destroy();
370     }
371 }
372