blob: b456ca00fe2b3f477ec7600295295e649607bd7a [file] [log] [blame]
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -08001/*
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
18package com.android.internal.app;
19
20import android.annotation.WorkerThread;
21import android.app.ActivityManager;
Hakan Seyalioglu9149dca2017-01-17 12:20:01 -080022import android.app.AppGlobals;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080023import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
Hakan Seyalioglu9149dca2017-01-17 12:20:01 -080026import android.content.IntentFilter;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080027import android.content.pm.ActivityInfo;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080028import android.content.pm.PackageManager;
29import android.content.pm.ResolveInfo;
Hakan Seyalioglu9149dca2017-01-17 12:20:01 -080030import android.os.RemoteException;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080031import android.util.Log;
Matt Pietaldf634cc2019-03-13 09:55:28 -040032
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080033import com.android.internal.annotations.VisibleForTesting;
arangelovb0802dc2019-10-18 18:03:44 +010034import com.android.internal.app.chooser.DisplayResolveInfo;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080035
36import java.util.ArrayList;
37import java.util.Collections;
38import java.util.List;
Zhen Zhang4bd260a2019-10-10 16:34:31 -070039import java.util.PriorityQueue;
Matt Pietaldf634cc2019-03-13 09:55:28 -040040import java.util.concurrent.CountDownLatch;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080041
42/**
43 * A helper for the ResolverActivity that exposes methods to retrieve, filter and sort its list of
44 * resolvers.
45 */
46public class ResolverListController {
47
48 private final Context mContext;
49 private final PackageManager mpm;
50 private final int mLaunchedFromUid;
51
52 // Needed for sorting resolvers.
53 private final Intent mTargetIntent;
54 private final String mReferrerPackage;
55
56 private static final String TAG = "ResolverListController";
57 private static final boolean DEBUG = false;
58
George Hodulik30f0c6f2019-03-20 18:23:23 -070059 private AbstractResolverComparator mResolverComparator;
Kang Li6afa4f22017-06-23 12:54:38 -070060 private boolean isComputed = false;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080061
62 public ResolverListController(
63 Context context,
64 PackageManager pm,
65 Intent targetIntent,
66 String referrerPackage,
67 int launchedFromUid) {
George Hodulikc681ce42019-04-12 17:10:31 -070068 this(context, pm, targetIntent, referrerPackage, launchedFromUid,
69 new ResolverRankerServiceResolverComparator(
70 context, targetIntent, referrerPackage, null));
71 }
72
73 public ResolverListController(
74 Context context,
75 PackageManager pm,
76 Intent targetIntent,
77 String referrerPackage,
78 int launchedFromUid,
79 AbstractResolverComparator resolverComparator) {
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080080 mContext = context;
81 mpm = pm;
82 mLaunchedFromUid = launchedFromUid;
83 mTargetIntent = targetIntent;
84 mReferrerPackage = referrerPackage;
George Hodulikc681ce42019-04-12 17:10:31 -070085 mResolverComparator = resolverComparator;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080086 }
87
88 @VisibleForTesting
Hakan Seyalioglu96ba5882017-01-25 10:48:44 -080089 public ResolveInfo getLastChosen() throws RemoteException {
Hakan Seyalioglu9149dca2017-01-17 12:20:01 -080090 return AppGlobals.getPackageManager().getLastChosenActivity(
91 mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
92 PackageManager.MATCH_DEFAULT_ONLY);
93 }
94
95 @VisibleForTesting
Hakan Seyalioglu96ba5882017-01-25 10:48:44 -080096 public void setLastChosen(Intent intent, IntentFilter filter, int match)
Hakan Seyalioglu9149dca2017-01-17 12:20:01 -080097 throws RemoteException {
98 AppGlobals.getPackageManager().setLastChosenActivity(intent,
99 intent.resolveType(mContext.getContentResolver()),
100 PackageManager.MATCH_DEFAULT_ONLY,
101 filter, match, intent.getComponent());
102 }
103
104 @VisibleForTesting
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800105 public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntent(
106 boolean shouldGetResolvedFilter,
107 boolean shouldGetActivityMetadata,
108 List<Intent> intents) {
109 List<ResolverActivity.ResolvedComponentInfo> resolvedComponents = null;
110 for (int i = 0, N = intents.size(); i < N; i++) {
111 final Intent intent = intents.get(i);
Patrick Baumann577d4022018-01-31 16:55:10 +0000112 int flags = PackageManager.MATCH_DEFAULT_ONLY
113 | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
114 | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0);
Patrick Baumann0da85372018-02-02 16:07:35 -0800115 if (intent.isWebIntent()
Patrick Baumann577d4022018-01-31 16:55:10 +0000116 || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
117 flags |= PackageManager.MATCH_INSTANT;
118 }
119 final List<ResolveInfo> infos = mpm.queryIntentActivities(intent, flags);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800120 if (infos != null) {
121 if (resolvedComponents == null) {
122 resolvedComponents = new ArrayList<>();
123 }
124 addResolveListDedupe(resolvedComponents, intent, infos);
125 }
126 }
127 return resolvedComponents;
128 }
129
130 @VisibleForTesting
131 public void addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into,
132 Intent intent,
133 List<ResolveInfo> from) {
134 final int fromCount = from.size();
135 final int intoCount = into.size();
136 for (int i = 0; i < fromCount; i++) {
137 final ResolveInfo newInfo = from.get(i);
138 boolean found = false;
139 // Only loop to the end of into as it was before we started; no dupes in from.
140 for (int j = 0; j < intoCount; j++) {
141 final ResolverActivity.ResolvedComponentInfo rci = into.get(j);
142 if (isSameResolvedComponent(newInfo, rci)) {
143 found = true;
144 rci.add(intent, newInfo);
145 break;
146 }
147 }
148 if (!found) {
149 final ComponentName name = new ComponentName(
150 newInfo.activityInfo.packageName, newInfo.activityInfo.name);
151 final ResolverActivity.ResolvedComponentInfo rci =
152 new ResolverActivity.ResolvedComponentInfo(name, intent, newInfo);
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500153 rci.setPinned(isComponentPinned(name));
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800154 into.add(rci);
155 }
156 }
157 }
158
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500159
160 /**
161 * Whether this component is pinned by the user. Always false for resolver; overridden in
162 * Chooser.
163 */
164 public boolean isComponentPinned(ComponentName name) {
165 return false;
166 }
167
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800168 // Filter out any activities that the launched uid does not have permission for.
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800169 // To preserve the inputList, optionally will return the original list if any modification has
170 // been made.
171 @VisibleForTesting
172 public ArrayList<ResolverActivity.ResolvedComponentInfo> filterIneligibleActivities(
173 List<ResolverActivity.ResolvedComponentInfo> inputList,
174 boolean returnCopyOfOriginalListIfModified) {
175 ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null;
176 for (int i = inputList.size()-1; i >= 0; i--) {
177 ActivityInfo ai = inputList.get(i)
178 .getResolveInfoAt(0).activityInfo;
179 int granted = ActivityManager.checkComponentPermission(
180 ai.permission, mLaunchedFromUid,
181 ai.applicationInfo.uid, ai.exported);
Matt Pietala4b30072019-04-04 13:44:36 -0400182
183 if (granted != PackageManager.PERMISSION_GRANTED
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800184 || isComponentFiltered(ai.getComponentName())) {
185 // Access not allowed! We're about to filter an item,
186 // so modify the unfiltered version if it hasn't already been modified.
187 if (returnCopyOfOriginalListIfModified && listToReturn == null) {
188 listToReturn = new ArrayList<>(inputList);
189 }
190 inputList.remove(i);
191 }
192 }
193 return listToReturn;
194 }
195
196 // Filter out any low priority items.
197 //
198 // To preserve the inputList, optionally will return the original list if any modification has
199 // been made.
200 @VisibleForTesting
201 public ArrayList<ResolverActivity.ResolvedComponentInfo> filterLowPriority(
202 List<ResolverActivity.ResolvedComponentInfo> inputList,
203 boolean returnCopyOfOriginalListIfModified) {
204 ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null;
205 // Only display the first matches that are either of equal
206 // priority or have asked to be default options.
207 ResolverActivity.ResolvedComponentInfo rci0 = inputList.get(0);
208 ResolveInfo r0 = rci0.getResolveInfoAt(0);
209 int N = inputList.size();
210 for (int i = 1; i < N; i++) {
211 ResolveInfo ri = inputList.get(i).getResolveInfoAt(0);
212 if (DEBUG) Log.v(
213 TAG,
214 r0.activityInfo.name + "=" +
215 r0.priority + "/" + r0.isDefault + " vs " +
216 ri.activityInfo.name + "=" +
217 ri.priority + "/" + ri.isDefault);
218 if (r0.priority != ri.priority ||
219 r0.isDefault != ri.isDefault) {
220 while (i < N) {
221 if (returnCopyOfOriginalListIfModified && listToReturn == null) {
222 listToReturn = new ArrayList<>(inputList);
223 }
224 inputList.remove(i);
225 N--;
226 }
227 }
228 }
229 return listToReturn;
230 }
231
George Hodulik30f0c6f2019-03-20 18:23:23 -0700232 private class ComputeCallback implements AbstractResolverComparator.AfterCompute {
Kang Li38a6da642017-04-05 12:30:55 -0700233
234 private CountDownLatch mFinishComputeSignal;
235
236 public ComputeCallback(CountDownLatch finishComputeSignal) {
237 mFinishComputeSignal = finishComputeSignal;
238 }
239
240 public void afterCompute () {
241 mFinishComputeSignal.countDown();
242 }
243 }
244
Zhen Zhang4bd260a2019-10-10 16:34:31 -0700245 private void compute(List<ResolverActivity.ResolvedComponentInfo> inputList)
246 throws InterruptedException {
Kang Lie3b47102018-02-09 11:51:23 -0800247 if (mResolverComparator == null) {
248 Log.d(TAG, "Comparator has already been destroyed; skipped.");
249 return;
250 }
Zhen Zhang4bd260a2019-10-10 16:34:31 -0700251 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
259 @VisibleForTesting
260 @WorkerThread
261 public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) {
Kang Lie3b47102018-02-09 11:51:23 -0800262 try {
263 long beforeRank = System.currentTimeMillis();
264 if (!isComputed) {
Zhen Zhang4bd260a2019-10-10 16:34:31 -0700265 compute(inputList);
Kang Li38a6da642017-04-05 12:30:55 -0700266 }
Kang Lie3b47102018-02-09 11:51:23 -0800267 Collections.sort(inputList, mResolverComparator);
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400268
Kang Lie3b47102018-02-09 11:51:23 -0800269 long afterRank = System.currentTimeMillis();
270 if (DEBUG) {
271 Log.d(TAG, "Time Cost: " + Long.toString(afterRank - beforeRank));
Kang Li6afa4f22017-06-23 12:54:38 -0700272 }
Kang Lie3b47102018-02-09 11:51:23 -0800273 } catch (InterruptedException e) {
274 Log.e(TAG, "Compute & Sort was interrupted: " + e);
Kang Li38a6da642017-04-05 12:30:55 -0700275 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800276 }
277
Zhen Zhang4bd260a2019-10-10 16:34:31 -0700278 @VisibleForTesting
279 @WorkerThread
280 public void topK(List<ResolverActivity.ResolvedComponentInfo> inputList, int k) {
281 if (inputList == null || inputList.isEmpty() || k <= 0) {
282 return;
283 }
284 if (inputList.size() <= k) {
285 // Fall into normal sort when number of ranked elements
286 // needed is not smaller than size of input list.
287 sort(inputList);
Zhen Zhange16ca902019-10-22 00:38:58 -0700288 return;
Zhen Zhang4bd260a2019-10-10 16:34:31 -0700289 }
290 try {
291 long beforeRank = System.currentTimeMillis();
292 if (!isComputed) {
293 compute(inputList);
294 }
295
296 // Top of this heap has lowest rank.
297 PriorityQueue<ResolverActivity.ResolvedComponentInfo> minHeap = new PriorityQueue<>(k,
298 (o1, o2) -> -mResolverComparator.compare(o1, o2));
299 final int size = inputList.size();
300 // Use this pointer to keep track of the position of next element
301 // to update in input list, starting from the last position.
302 int pointer = size - 1;
303 minHeap.addAll(inputList.subList(size - k, size));
304 for (int i = size - k - 1; i >= 0; --i) {
305 ResolverActivity.ResolvedComponentInfo ci = inputList.get(i);
306 if (-mResolverComparator.compare(ci, minHeap.peek()) > 0) {
307 // When ranked higher than top of heap, remove top of heap,
308 // update input list with it, add this new element to heap.
309 inputList.set(pointer--, minHeap.poll());
310 minHeap.add(ci);
311 } else {
312 // When ranked no higher than top of heap, update input list
313 // with this new element.
314 inputList.set(pointer--, ci);
315 }
316 }
317
318 // Now we have top k elements in heap, update first
319 // k positions of input list with them.
320 while (!minHeap.isEmpty()) {
321 inputList.set(pointer--, minHeap.poll());
322 }
323
324 long afterRank = System.currentTimeMillis();
325 if (DEBUG) {
326 Log.d(TAG, "Time Cost for top " + k + " targets: "
327 + Long.toString(afterRank - beforeRank));
328 }
329 } catch (InterruptedException e) {
330 Log.e(TAG, "Compute & greatestOf was interrupted: " + e);
331 }
332 }
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400333
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800334 private static boolean isSameResolvedComponent(ResolveInfo a,
335 ResolverActivity.ResolvedComponentInfo b) {
336 final ActivityInfo ai = a.activityInfo;
337 return ai.packageName.equals(b.name.getPackageName())
338 && ai.name.equals(b.name.getClassName());
339 }
340
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800341 boolean isComponentFiltered(ComponentName componentName) {
342 return false;
343 }
344
345 @VisibleForTesting
arangelovb0802dc2019-10-18 18:03:44 +0100346 public float getScore(DisplayResolveInfo target) {
Kang Lie3b47102018-02-09 11:51:23 -0800347 return mResolverComparator.getScore(target.getResolvedComponentName());
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800348 }
Kang Li0cef910d2017-01-05 09:14:36 -0800349
350 public void updateModel(ComponentName componentName) {
Kang Lie3b47102018-02-09 11:51:23 -0800351 mResolverComparator.updateModel(componentName);
Kang Li0cef910d2017-01-05 09:14:36 -0800352 }
Kang Li9fa2a2c2017-01-06 13:33:24 -0800353
354 public void updateChooserCounts(String packageName, int userId, String action) {
Kang Lie3b47102018-02-09 11:51:23 -0800355 mResolverComparator.updateChooserCounts(packageName, userId, action);
Kang Li9fa2a2c2017-01-06 13:33:24 -0800356 }
Kang Li38a6da642017-04-05 12:30:55 -0700357
358 public void destroy() {
Kang Lie3b47102018-02-09 11:51:23 -0800359 mResolverComparator.destroy();
Kang Li38a6da642017-04-05 12:30:55 -0700360 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800361}