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