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.util.Log;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.concurrent.CountDownLatch;
39 
40 /**
41  * A helper for the ResolverActivity that exposes methods to retrieve, filter and sort its list of
42  * resolvers.
43  */
44 public class ResolverListController {
45 
46     private final Context mContext;
47     private final PackageManager mpm;
48     private final int mLaunchedFromUid;
49 
50     // Needed for sorting resolvers.
51     private final Intent mTargetIntent;
52     private final String mReferrerPackage;
53 
54     private static final String TAG = "ResolverListController";
55     private static final boolean DEBUG = false;
56 
57     private AbstractResolverComparator mResolverComparator;
58     private boolean isComputed = false;
59 
ResolverListController( Context context, PackageManager pm, Intent targetIntent, String referrerPackage, int launchedFromUid)60     public ResolverListController(
61             Context context,
62             PackageManager pm,
63             Intent targetIntent,
64             String referrerPackage,
65             int launchedFromUid) {
66         this(context, pm, targetIntent, referrerPackage, launchedFromUid,
67                     new ResolverRankerServiceResolverComparator(
68                         context, targetIntent, referrerPackage, null));
69     }
70 
ResolverListController( Context context, PackageManager pm, Intent targetIntent, String referrerPackage, int launchedFromUid, AbstractResolverComparator resolverComparator)71     public ResolverListController(
72             Context context,
73             PackageManager pm,
74             Intent targetIntent,
75             String referrerPackage,
76             int launchedFromUid,
77             AbstractResolverComparator resolverComparator) {
78         mContext = context;
79         mpm = pm;
80         mLaunchedFromUid = launchedFromUid;
81         mTargetIntent = targetIntent;
82         mReferrerPackage = referrerPackage;
83         mResolverComparator = resolverComparator;
84     }
85 
86     @VisibleForTesting
getLastChosen()87     public ResolveInfo getLastChosen() throws RemoteException {
88         return AppGlobals.getPackageManager().getLastChosenActivity(
89                 mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
90                 PackageManager.MATCH_DEFAULT_ONLY);
91     }
92 
93     @VisibleForTesting
setLastChosen(Intent intent, IntentFilter filter, int match)94     public void setLastChosen(Intent intent, IntentFilter filter, int match)
95             throws RemoteException {
96         AppGlobals.getPackageManager().setLastChosenActivity(intent,
97                 intent.resolveType(mContext.getContentResolver()),
98                 PackageManager.MATCH_DEFAULT_ONLY,
99                 filter, match, intent.getComponent());
100     }
101 
102     @VisibleForTesting
getResolversForIntent( boolean shouldGetResolvedFilter, boolean shouldGetActivityMetadata, List<Intent> intents)103     public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntent(
104             boolean shouldGetResolvedFilter,
105             boolean shouldGetActivityMetadata,
106             List<Intent> intents) {
107         List<ResolverActivity.ResolvedComponentInfo> resolvedComponents = null;
108         for (int i = 0, N = intents.size(); i < N; i++) {
109             final Intent intent = intents.get(i);
110             int flags = PackageManager.MATCH_DEFAULT_ONLY
111                     | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
112                     | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0);
113             if (intent.isWebIntent()
114                         || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
115                 flags |= PackageManager.MATCH_INSTANT;
116             }
117             final List<ResolveInfo> infos = mpm.queryIntentActivities(intent, flags);
118             // Remove any activities that are not exported.
119             int totalSize = infos.size();
120             for (int j = totalSize - 1; j >= 0 ; j--) {
121                 ResolveInfo info = infos.get(j);
122                 if (info.activityInfo != null && !info.activityInfo.exported) {
123                     infos.remove(j);
124                 }
125             }
126             if (infos != null) {
127                 if (resolvedComponents == null) {
128                     resolvedComponents = new ArrayList<>();
129                 }
130                 addResolveListDedupe(resolvedComponents, intent, infos);
131             }
132         }
133         return resolvedComponents;
134     }
135 
136     @VisibleForTesting
addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into, Intent intent, List<ResolveInfo> from)137     public void addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into,
138             Intent intent,
139             List<ResolveInfo> from) {
140         final int fromCount = from.size();
141         final int intoCount = into.size();
142         for (int i = 0; i < fromCount; i++) {
143             final ResolveInfo newInfo = from.get(i);
144             boolean found = false;
145             // Only loop to the end of into as it was before we started; no dupes in from.
146             for (int j = 0; j < intoCount; j++) {
147                 final ResolverActivity.ResolvedComponentInfo rci = into.get(j);
148                 if (isSameResolvedComponent(newInfo, rci)) {
149                     found = true;
150                     rci.add(intent, newInfo);
151                     break;
152                 }
153             }
154             if (!found) {
155                 final ComponentName name = new ComponentName(
156                         newInfo.activityInfo.packageName, newInfo.activityInfo.name);
157                 final ResolverActivity.ResolvedComponentInfo rci =
158                         new ResolverActivity.ResolvedComponentInfo(name, intent, newInfo);
159                 rci.setPinned(isComponentPinned(name));
160                 into.add(rci);
161             }
162         }
163     }
164 
165 
166     /**
167      * Whether this component is pinned by the user. Always false for resolver; overridden in
168      * Chooser.
169      */
isComponentPinned(ComponentName name)170     public boolean isComponentPinned(ComponentName name) {
171         return false;
172     }
173 
174 
175     // Filter out any activities that the launched uid does not have permission for.
176     // To preserve the inputList, optionally will return the original list if any modification has
177     // been made.
178     @VisibleForTesting
filterIneligibleActivities( List<ResolverActivity.ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified)179     public ArrayList<ResolverActivity.ResolvedComponentInfo> filterIneligibleActivities(
180             List<ResolverActivity.ResolvedComponentInfo> inputList,
181             boolean returnCopyOfOriginalListIfModified) {
182         ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null;
183         for (int i = inputList.size()-1; i >= 0; i--) {
184             ActivityInfo ai = inputList.get(i)
185                     .getResolveInfoAt(0).activityInfo;
186             int granted = ActivityManager.checkComponentPermission(
187                     ai.permission, mLaunchedFromUid,
188                     ai.applicationInfo.uid, ai.exported);
189 
190             if (granted != PackageManager.PERMISSION_GRANTED
191                     || isComponentFiltered(ai.getComponentName())) {
192                 // Access not allowed! We're about to filter an item,
193                 // so modify the unfiltered version if it hasn't already been modified.
194                 if (returnCopyOfOriginalListIfModified && listToReturn == null) {
195                     listToReturn = new ArrayList<>(inputList);
196                 }
197                 inputList.remove(i);
198             }
199         }
200         return listToReturn;
201     }
202 
203     // Filter out any low priority items.
204     //
205     // To preserve the inputList, optionally will return the original list if any modification has
206     // been made.
207     @VisibleForTesting
filterLowPriority( List<ResolverActivity.ResolvedComponentInfo> inputList, boolean returnCopyOfOriginalListIfModified)208     public ArrayList<ResolverActivity.ResolvedComponentInfo> filterLowPriority(
209             List<ResolverActivity.ResolvedComponentInfo> inputList,
210             boolean returnCopyOfOriginalListIfModified) {
211         ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null;
212         // Only display the first matches that are either of equal
213         // priority or have asked to be default options.
214         ResolverActivity.ResolvedComponentInfo rci0 = inputList.get(0);
215         ResolveInfo r0 = rci0.getResolveInfoAt(0);
216         int N = inputList.size();
217         for (int i = 1; i < N; i++) {
218             ResolveInfo ri = inputList.get(i).getResolveInfoAt(0);
219             if (DEBUG) Log.v(
220                     TAG,
221                     r0.activityInfo.name + "=" +
222                             r0.priority + "/" + r0.isDefault + " vs " +
223                             ri.activityInfo.name + "=" +
224                             ri.priority + "/" + ri.isDefault);
225             if (r0.priority != ri.priority ||
226                     r0.isDefault != ri.isDefault) {
227                 while (i < N) {
228                     if (returnCopyOfOriginalListIfModified && listToReturn == null) {
229                         listToReturn = new ArrayList<>(inputList);
230                     }
231                     inputList.remove(i);
232                     N--;
233                 }
234             }
235         }
236         return listToReturn;
237     }
238 
239     private class ComputeCallback implements AbstractResolverComparator.AfterCompute {
240 
241         private CountDownLatch mFinishComputeSignal;
242 
ComputeCallback(CountDownLatch finishComputeSignal)243         public ComputeCallback(CountDownLatch finishComputeSignal) {
244             mFinishComputeSignal = finishComputeSignal;
245         }
246 
afterCompute()247         public void afterCompute () {
248             mFinishComputeSignal.countDown();
249         }
250     }
251 
252     @VisibleForTesting
253     @WorkerThread
sort(List<ResolverActivity.ResolvedComponentInfo> inputList)254     public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) {
255         if (mResolverComparator == null) {
256             Log.d(TAG, "Comparator has already been destroyed; skipped.");
257             return;
258         }
259         try {
260             long beforeRank = System.currentTimeMillis();
261             if (!isComputed) {
262                 final CountDownLatch finishComputeSignal = new CountDownLatch(1);
263                 ComputeCallback callback = new ComputeCallback(finishComputeSignal);
264                 mResolverComparator.setCallBack(callback);
265                 mResolverComparator.compute(inputList);
266                 finishComputeSignal.await();
267                 isComputed = true;
268             }
269             Collections.sort(inputList, mResolverComparator);
270 
271             long afterRank = System.currentTimeMillis();
272             if (DEBUG) {
273                 Log.d(TAG, "Time Cost: " + Long.toString(afterRank - beforeRank));
274             }
275         } catch (InterruptedException e) {
276             Log.e(TAG, "Compute & Sort was interrupted: " + e);
277         }
278     }
279 
280 
isSameResolvedComponent(ResolveInfo a, ResolverActivity.ResolvedComponentInfo b)281     private static boolean isSameResolvedComponent(ResolveInfo a,
282             ResolverActivity.ResolvedComponentInfo b) {
283         final ActivityInfo ai = a.activityInfo;
284         return ai.packageName.equals(b.name.getPackageName())
285                 && ai.name.equals(b.name.getClassName());
286     }
287 
isComponentFiltered(ComponentName componentName)288     boolean isComponentFiltered(ComponentName componentName) {
289         return false;
290     }
291 
292     @VisibleForTesting
getScore(ResolverActivity.DisplayResolveInfo target)293     public float getScore(ResolverActivity.DisplayResolveInfo target) {
294         return mResolverComparator.getScore(target.getResolvedComponentName());
295     }
296 
updateModel(ComponentName componentName)297     public void updateModel(ComponentName componentName) {
298         mResolverComparator.updateModel(componentName);
299     }
300 
updateChooserCounts(String packageName, int userId, String action)301     public void updateChooserCounts(String packageName, int userId, String action) {
302         mResolverComparator.updateChooserCounts(packageName, userId, action);
303     }
304 
destroy()305     public void destroy() {
306         mResolverComparator.destroy();
307     }
308 }
309