Home | History | Annotate | Download | only in quickcontact
      1 /*
      2  * Copyright (C) 2010 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 package com.android.contacts.quickcontact;
     18 
     19 import com.android.contacts.util.PhoneCapabilityTester;
     20 import com.google.android.collect.Sets;
     21 
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.content.pm.ApplicationInfo;
     26 import android.content.pm.PackageManager;
     27 import android.content.pm.ResolveInfo;
     28 import android.graphics.drawable.Drawable;
     29 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
     30 import android.text.TextUtils;
     31 
     32 import java.lang.ref.SoftReference;
     33 import java.util.HashMap;
     34 import java.util.HashSet;
     35 import java.util.List;
     36 
     37 /**
     38  * Internally hold a cache of scaled icons based on {@link PackageManager}
     39  * queries, keyed internally on MIME-type.
     40  */
     41 public class ResolveCache {
     42     /**
     43      * Specific list {@link ApplicationInfo#packageName} of apps that are
     44      * prefered <strong>only</strong> for the purposes of default icons when
     45      * multiple {@link ResolveInfo} are found to match. This only happens when
     46      * the user has not selected a default app yet, and they will still be
     47      * presented with the system disambiguation dialog.
     48      */
     49     private static final HashSet<String> sPreferResolve = Sets.newHashSet(
     50             "com.android.email",
     51             "com.android.calendar",
     52             "com.android.contacts",
     53             "com.android.mms",
     54             "com.android.phone",
     55             "com.android.browser");
     56 
     57     private final Context mContext;
     58     private final PackageManager mPackageManager;
     59 
     60     private static ResolveCache sInstance;
     61 
     62     /**
     63      * Returns an instance of the ResolveCache. Only one internal instance is kept, so
     64      * the argument packageManagers is ignored for all but the first call
     65      */
     66     public synchronized static ResolveCache getInstance(Context context) {
     67         if (sInstance == null) {
     68             return sInstance = new ResolveCache(context.getApplicationContext());
     69         }
     70         return sInstance;
     71     }
     72 
     73     public synchronized static void flush() {
     74         sInstance = null;
     75     }
     76 
     77     /**
     78      * Cached entry holding the best {@link ResolveInfo} for a specific
     79      * MIME-type, along with a {@link SoftReference} to its icon.
     80      */
     81     private static class Entry {
     82         public ResolveInfo bestResolve;
     83         public Drawable icon;
     84     }
     85 
     86     private HashMap<String, Entry> mCache = new HashMap<String, Entry>();
     87 
     88 
     89     private ResolveCache(Context context) {
     90         mContext = context;
     91         mPackageManager = context.getPackageManager();
     92     }
     93 
     94     /**
     95      * Get the {@link Entry} best associated with the given {@link Action},
     96      * or create and populate a new one if it doesn't exist.
     97      */
     98     protected Entry getEntry(Action action) {
     99         final String mimeType = action.getMimeType();
    100         Entry entry = mCache.get(mimeType);
    101         if (entry != null) return entry;
    102         entry = new Entry();
    103 
    104         Intent intent = action.getIntent();
    105         if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType)
    106                 && !PhoneCapabilityTester.isSipPhone(mContext)) {
    107             intent = null;
    108         }
    109 
    110         if (intent != null) {
    111             final List<ResolveInfo> matches = mPackageManager.queryIntentActivities(intent,
    112                     PackageManager.MATCH_DEFAULT_ONLY);
    113 
    114             // Pick first match, otherwise best found
    115             ResolveInfo bestResolve = null;
    116             final int size = matches.size();
    117             if (size == 1) {
    118                 bestResolve = matches.get(0);
    119             } else if (size > 1) {
    120                 bestResolve = getBestResolve(intent, matches);
    121             }
    122 
    123             if (bestResolve != null) {
    124                 final Drawable icon = bestResolve.loadIcon(mPackageManager);
    125 
    126                 entry.bestResolve = bestResolve;
    127                 entry.icon = icon;
    128             }
    129         }
    130 
    131         mCache.put(mimeType, entry);
    132         return entry;
    133     }
    134 
    135     /**
    136      * Best {@link ResolveInfo} when multiple found. Ties are broken by
    137      * selecting first from the {@link QuickContactActivity#sPreferResolve} list of
    138      * preferred packages, second by apps that live on the system partition,
    139      * otherwise the app from the top of the list. This is
    140      * <strong>only</strong> used for selecting a default icon for
    141      * displaying in the track, and does not shortcut the system
    142      * {@link Intent} disambiguation dialog.
    143      */
    144     protected ResolveInfo getBestResolve(Intent intent, List<ResolveInfo> matches) {
    145         // Try finding preferred activity, otherwise detect disambig
    146         final ResolveInfo foundResolve = mPackageManager.resolveActivity(intent,
    147                 PackageManager.MATCH_DEFAULT_ONLY);
    148         final boolean foundDisambig = (foundResolve.match &
    149                 IntentFilter.MATCH_CATEGORY_MASK) == 0;
    150 
    151         if (!foundDisambig) {
    152             // Found concrete match, so return directly
    153             return foundResolve;
    154         }
    155 
    156         // Accept any package from prefer list, otherwise first system app
    157         ResolveInfo firstSystem = null;
    158         for (ResolveInfo info : matches) {
    159             final boolean isSystem = (info.activityInfo.applicationInfo.flags
    160                     & ApplicationInfo.FLAG_SYSTEM) != 0;
    161             final boolean isPrefer = sPreferResolve
    162                     .contains(info.activityInfo.applicationInfo.packageName);
    163 
    164             if (isPrefer) return info;
    165             if (isSystem && firstSystem == null) firstSystem = info;
    166         }
    167 
    168         // Return first system found, otherwise first from list
    169         return firstSystem != null ? firstSystem : matches.get(0);
    170     }
    171 
    172     /**
    173      * Check {@link PackageManager} to see if any apps offer to handle the
    174      * given {@link Action}.
    175      */
    176     public boolean hasResolve(Action action) {
    177         return getEntry(action).bestResolve != null;
    178     }
    179 
    180     /**
    181      * Find the best description for the given {@link Action}, usually used
    182      * for accessibility purposes.
    183      */
    184     public CharSequence getDescription(Action action) {
    185         final CharSequence actionSubtitle = action.getSubtitle();
    186         final ResolveInfo info = getEntry(action).bestResolve;
    187         if (info != null) {
    188             return info.loadLabel(mPackageManager);
    189         } else if (!TextUtils.isEmpty(actionSubtitle)) {
    190             return actionSubtitle;
    191         } else {
    192             return null;
    193         }
    194     }
    195 
    196     /**
    197      * Return the best icon for the given {@link Action}, which is usually
    198      * based on the {@link ResolveInfo} found through a
    199      * {@link PackageManager} query.
    200      */
    201     public Drawable getIcon(Action action) {
    202         return getEntry(action).icon;
    203     }
    204 
    205     public void clear() {
    206         mCache.clear();
    207     }
    208 }
    209