Home | History | Annotate | Download | only in app
      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 
     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
     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
     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
     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
    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
    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
    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 
    230         public ComputeCallback(CountDownLatch finishComputeSignal) {
    231             mFinishComputeSignal = finishComputeSignal;
    232         }
    233 
    234         public void afterCompute () {
    235             mFinishComputeSignal.countDown();
    236         }
    237     }
    238 
    239     @VisibleForTesting
    240     @WorkerThread
    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 
    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 
    273     boolean isComponentPinned(ComponentName name) {
    274         return false;
    275     }
    276 
    277     boolean isComponentFiltered(ComponentName componentName) {
    278         return false;
    279     }
    280 
    281     @VisibleForTesting
    282     public float getScore(ResolverActivity.DisplayResolveInfo target) {
    283         return mResolverComparator.getScore(target.getResolvedComponentName());
    284     }
    285 
    286     public void updateModel(ComponentName componentName) {
    287         mResolverComparator.updateModel(componentName);
    288     }
    289 
    290     public void updateChooserCounts(String packageName, int userId, String action) {
    291         mResolverComparator.updateChooserCounts(packageName, userId, action);
    292     }
    293 
    294     public void destroy() {
    295         mResolverComparator.destroy();
    296     }
    297 }
    298