blob: abd3eb2453dfe9f2931eeac0dc9c087303f40147 [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;
arangelov38a6fce2019-12-02 18:21:22 +000031import android.os.UserHandle;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080032import android.util.Log;
Matt Pietaldf634cc2019-03-13 09:55:28 -040033
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080034import com.android.internal.annotations.VisibleForTesting;
arangelovb0802dc2019-10-18 18:03:44 +010035import com.android.internal.app.chooser.DisplayResolveInfo;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080036
37import java.util.ArrayList;
38import java.util.Collections;
39import java.util.List;
Zhen Zhang4bd260a2019-10-10 16:34:31 -070040import java.util.PriorityQueue;
Matt Pietaldf634cc2019-03-13 09:55:28 -040041import java.util.concurrent.CountDownLatch;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080042
43/**
44 * A helper for the ResolverActivity that exposes methods to retrieve, filter and sort its list of
45 * resolvers.
46 */
47public 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;
arangelov38a6fce2019-12-02 18:21:22 +000059 private final UserHandle mUserHandle;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080060
George Hodulik30f0c6f2019-03-20 18:23:23 -070061 private AbstractResolverComparator mResolverComparator;
Kang Li6afa4f22017-06-23 12:54:38 -070062 private boolean isComputed = false;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080063
64 public ResolverListController(
65 Context context,
66 PackageManager pm,
67 Intent targetIntent,
68 String referrerPackage,
arangelov38a6fce2019-12-02 18:21:22 +000069 int launchedFromUid,
70 UserHandle userHandle) {
71 this(context, pm, targetIntent, referrerPackage, launchedFromUid, userHandle,
George Hodulikc681ce42019-04-12 17:10:31 -070072 new ResolverRankerServiceResolverComparator(
73 context, targetIntent, referrerPackage, null));
74 }
75
76 public ResolverListController(
77 Context context,
78 PackageManager pm,
79 Intent targetIntent,
80 String referrerPackage,
81 int launchedFromUid,
arangelov38a6fce2019-12-02 18:21:22 +000082 UserHandle userHandle,
George Hodulikc681ce42019-04-12 17:10:31 -070083 AbstractResolverComparator resolverComparator) {
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080084 mContext = context;
85 mpm = pm;
86 mLaunchedFromUid = launchedFromUid;
87 mTargetIntent = targetIntent;
88 mReferrerPackage = referrerPackage;
arangelov38a6fce2019-12-02 18:21:22 +000089 mUserHandle = userHandle;
George Hodulikc681ce42019-04-12 17:10:31 -070090 mResolverComparator = resolverComparator;
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -080091 }
92
93 @VisibleForTesting
Hakan Seyalioglu96ba5882017-01-25 10:48:44 -080094 public ResolveInfo getLastChosen() throws RemoteException {
Hakan Seyalioglu9149dca2017-01-17 12:20:01 -080095 return AppGlobals.getPackageManager().getLastChosenActivity(
96 mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
97 PackageManager.MATCH_DEFAULT_ONLY);
98 }
99
100 @VisibleForTesting
Hakan Seyalioglu96ba5882017-01-25 10:48:44 -0800101 public void setLastChosen(Intent intent, IntentFilter filter, int match)
Hakan Seyalioglu9149dca2017-01-17 12:20:01 -0800102 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
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800110 public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntent(
111 boolean shouldGetResolvedFilter,
112 boolean shouldGetActivityMetadata,
113 List<Intent> intents) {
114 List<ResolverActivity.ResolvedComponentInfo> resolvedComponents = null;
115 for (int i = 0, N = intents.size(); i < N; i++) {
116 final Intent intent = intents.get(i);
Patrick Baumann577d4022018-01-31 16:55:10 +0000117 int flags = PackageManager.MATCH_DEFAULT_ONLY
118 | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
119 | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0);
Patrick Baumann0da85372018-02-02 16:07:35 -0800120 if (intent.isWebIntent()
Patrick Baumann577d4022018-01-31 16:55:10 +0000121 || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
122 flags |= PackageManager.MATCH_INSTANT;
123 }
arangelov38a6fce2019-12-02 18:21:22 +0000124 final List<ResolveInfo> infos = mpm.queryIntentActivitiesAsUser(intent, flags,
125 mUserHandle);
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800126 if (infos != null) {
127 if (resolvedComponents == null) {
128 resolvedComponents = new ArrayList<>();
129 }
130 addResolveListDedupe(resolvedComponents, intent, infos);
131 }
132 }
133 return resolvedComponents;
134 }
135
arangelov38a6fce2019-12-02 18:21:22 +0000136 UserHandle getUserHandle() {
137 return mUserHandle;
138 }
139
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800140 @VisibleForTesting
141 public void addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into,
142 Intent intent,
143 List<ResolveInfo> from) {
144 final int fromCount = from.size();
145 final int intoCount = into.size();
146 for (int i = 0; i < fromCount; i++) {
147 final ResolveInfo newInfo = from.get(i);
148 boolean found = false;
149 // Only loop to the end of into as it was before we started; no dupes in from.
150 for (int j = 0; j < intoCount; j++) {
151 final ResolverActivity.ResolvedComponentInfo rci = into.get(j);
152 if (isSameResolvedComponent(newInfo, rci)) {
153 found = true;
154 rci.add(intent, newInfo);
155 break;
156 }
157 }
158 if (!found) {
159 final ComponentName name = new ComponentName(
160 newInfo.activityInfo.packageName, newInfo.activityInfo.name);
161 final ResolverActivity.ResolvedComponentInfo rci =
162 new ResolverActivity.ResolvedComponentInfo(name, intent, newInfo);
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500163 rci.setPinned(isComponentPinned(name));
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800164 into.add(rci);
165 }
166 }
167 }
168
Alison Cichowlas1fd47152019-11-14 19:50:55 -0500169
170 /**
171 * Whether this component is pinned by the user. Always false for resolver; overridden in
172 * Chooser.
173 */
174 public boolean isComponentPinned(ComponentName name) {
175 return false;
176 }
177
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800178 // Filter out any activities that the launched uid does not have permission for.
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800179 // To preserve the inputList, optionally will return the original list if any modification has
180 // been made.
181 @VisibleForTesting
182 public ArrayList<ResolverActivity.ResolvedComponentInfo> filterIneligibleActivities(
183 List<ResolverActivity.ResolvedComponentInfo> inputList,
184 boolean returnCopyOfOriginalListIfModified) {
185 ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null;
186 for (int i = inputList.size()-1; i >= 0; i--) {
187 ActivityInfo ai = inputList.get(i)
188 .getResolveInfoAt(0).activityInfo;
189 int granted = ActivityManager.checkComponentPermission(
190 ai.permission, mLaunchedFromUid,
191 ai.applicationInfo.uid, ai.exported);
Matt Pietala4b30072019-04-04 13:44:36 -0400192
193 if (granted != PackageManager.PERMISSION_GRANTED
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800194 || isComponentFiltered(ai.getComponentName())) {
195 // Access not allowed! We're about to filter an item,
196 // so modify the unfiltered version if it hasn't already been modified.
197 if (returnCopyOfOriginalListIfModified && listToReturn == null) {
198 listToReturn = new ArrayList<>(inputList);
199 }
200 inputList.remove(i);
201 }
202 }
203 return listToReturn;
204 }
205
206 // Filter out any low priority items.
207 //
208 // To preserve the inputList, optionally will return the original list if any modification has
209 // been made.
210 @VisibleForTesting
211 public ArrayList<ResolverActivity.ResolvedComponentInfo> filterLowPriority(
212 List<ResolverActivity.ResolvedComponentInfo> inputList,
213 boolean returnCopyOfOriginalListIfModified) {
214 ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null;
215 // Only display the first matches that are either of equal
216 // priority or have asked to be default options.
217 ResolverActivity.ResolvedComponentInfo rci0 = inputList.get(0);
218 ResolveInfo r0 = rci0.getResolveInfoAt(0);
219 int N = inputList.size();
220 for (int i = 1; i < N; i++) {
221 ResolveInfo ri = inputList.get(i).getResolveInfoAt(0);
222 if (DEBUG) Log.v(
223 TAG,
224 r0.activityInfo.name + "=" +
225 r0.priority + "/" + r0.isDefault + " vs " +
226 ri.activityInfo.name + "=" +
227 ri.priority + "/" + ri.isDefault);
228 if (r0.priority != ri.priority ||
229 r0.isDefault != ri.isDefault) {
230 while (i < N) {
231 if (returnCopyOfOriginalListIfModified && listToReturn == null) {
232 listToReturn = new ArrayList<>(inputList);
233 }
234 inputList.remove(i);
235 N--;
236 }
237 }
238 }
239 return listToReturn;
240 }
241
George Hodulik30f0c6f2019-03-20 18:23:23 -0700242 private class ComputeCallback implements AbstractResolverComparator.AfterCompute {
Kang Li38a6da642017-04-05 12:30:55 -0700243
244 private CountDownLatch mFinishComputeSignal;
245
246 public ComputeCallback(CountDownLatch finishComputeSignal) {
247 mFinishComputeSignal = finishComputeSignal;
248 }
249
250 public void afterCompute () {
251 mFinishComputeSignal.countDown();
252 }
253 }
254
Zhen Zhang4bd260a2019-10-10 16:34:31 -0700255 private void compute(List<ResolverActivity.ResolvedComponentInfo> inputList)
256 throws InterruptedException {
Kang Lie3b47102018-02-09 11:51:23 -0800257 if (mResolverComparator == null) {
258 Log.d(TAG, "Comparator has already been destroyed; skipped.");
259 return;
260 }
Zhen Zhang4bd260a2019-10-10 16:34:31 -0700261 final CountDownLatch finishComputeSignal = new CountDownLatch(1);
262 ComputeCallback callback = new ComputeCallback(finishComputeSignal);
263 mResolverComparator.setCallBack(callback);
264 mResolverComparator.compute(inputList);
265 finishComputeSignal.await();
266 isComputed = true;
267 }
268
269 @VisibleForTesting
270 @WorkerThread
271 public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) {
Kang Lie3b47102018-02-09 11:51:23 -0800272 try {
273 long beforeRank = System.currentTimeMillis();
274 if (!isComputed) {
Zhen Zhang4bd260a2019-10-10 16:34:31 -0700275 compute(inputList);
Kang Li38a6da642017-04-05 12:30:55 -0700276 }
Kang Lie3b47102018-02-09 11:51:23 -0800277 Collections.sort(inputList, mResolverComparator);
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400278
Kang Lie3b47102018-02-09 11:51:23 -0800279 long afterRank = System.currentTimeMillis();
280 if (DEBUG) {
281 Log.d(TAG, "Time Cost: " + Long.toString(afterRank - beforeRank));
Kang Li6afa4f22017-06-23 12:54:38 -0700282 }
Kang Lie3b47102018-02-09 11:51:23 -0800283 } catch (InterruptedException e) {
284 Log.e(TAG, "Compute & Sort was interrupted: " + e);
Kang Li38a6da642017-04-05 12:30:55 -0700285 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800286 }
287
Zhen Zhang4bd260a2019-10-10 16:34:31 -0700288 @VisibleForTesting
289 @WorkerThread
290 public void topK(List<ResolverActivity.ResolvedComponentInfo> inputList, int k) {
291 if (inputList == null || inputList.isEmpty() || k <= 0) {
292 return;
293 }
294 if (inputList.size() <= k) {
295 // Fall into normal sort when number of ranked elements
296 // needed is not smaller than size of input list.
297 sort(inputList);
Zhen Zhange16ca902019-10-22 00:38:58 -0700298 return;
Zhen Zhang4bd260a2019-10-10 16:34:31 -0700299 }
300 try {
301 long beforeRank = System.currentTimeMillis();
302 if (!isComputed) {
303 compute(inputList);
304 }
305
306 // Top of this heap has lowest rank.
307 PriorityQueue<ResolverActivity.ResolvedComponentInfo> minHeap = new PriorityQueue<>(k,
308 (o1, o2) -> -mResolverComparator.compare(o1, o2));
309 final int size = inputList.size();
310 // Use this pointer to keep track of the position of next element
311 // to update in input list, starting from the last position.
312 int pointer = size - 1;
313 minHeap.addAll(inputList.subList(size - k, size));
314 for (int i = size - k - 1; i >= 0; --i) {
315 ResolverActivity.ResolvedComponentInfo ci = inputList.get(i);
316 if (-mResolverComparator.compare(ci, minHeap.peek()) > 0) {
317 // When ranked higher than top of heap, remove top of heap,
318 // update input list with it, add this new element to heap.
319 inputList.set(pointer--, minHeap.poll());
320 minHeap.add(ci);
321 } else {
322 // When ranked no higher than top of heap, update input list
323 // with this new element.
324 inputList.set(pointer--, ci);
325 }
326 }
327
328 // Now we have top k elements in heap, update first
329 // k positions of input list with them.
330 while (!minHeap.isEmpty()) {
331 inputList.set(pointer--, minHeap.poll());
332 }
333
334 long afterRank = System.currentTimeMillis();
335 if (DEBUG) {
336 Log.d(TAG, "Time Cost for top " + k + " targets: "
337 + Long.toString(afterRank - beforeRank));
338 }
339 } catch (InterruptedException e) {
340 Log.e(TAG, "Compute & greatestOf was interrupted: " + e);
341 }
342 }
Alison Cichowlas1c8816c2019-04-03 17:43:22 -0400343
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800344 private static boolean isSameResolvedComponent(ResolveInfo a,
345 ResolverActivity.ResolvedComponentInfo b) {
346 final ActivityInfo ai = a.activityInfo;
347 return ai.packageName.equals(b.name.getPackageName())
348 && ai.name.equals(b.name.getClassName());
349 }
350
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800351 boolean isComponentFiltered(ComponentName componentName) {
352 return false;
353 }
354
355 @VisibleForTesting
arangelovb0802dc2019-10-18 18:03:44 +0100356 public float getScore(DisplayResolveInfo target) {
Kang Lie3b47102018-02-09 11:51:23 -0800357 return mResolverComparator.getScore(target.getResolvedComponentName());
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800358 }
Kang Li0cef910d2017-01-05 09:14:36 -0800359
360 public void updateModel(ComponentName componentName) {
Kang Lie3b47102018-02-09 11:51:23 -0800361 mResolverComparator.updateModel(componentName);
Kang Li0cef910d2017-01-05 09:14:36 -0800362 }
Kang Li9fa2a2c2017-01-06 13:33:24 -0800363
364 public void updateChooserCounts(String packageName, int userId, String action) {
Kang Lie3b47102018-02-09 11:51:23 -0800365 mResolverComparator.updateChooserCounts(packageName, userId, action);
Kang Li9fa2a2c2017-01-06 13:33:24 -0800366 }
Kang Li38a6da642017-04-05 12:30:55 -0700367
368 public void destroy() {
Kang Lie3b47102018-02-09 11:51:23 -0800369 mResolverComparator.destroy();
Kang Li38a6da642017-04-05 12:30:55 -0700370 }
Hakan Seyalioglue1276bf2016-12-07 16:38:57 -0800371}