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