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