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