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