• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
getInstance(Context context)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 
flush()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 
ResolveCache(Context context)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      */
getEntry(Action action)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      */
getBestResolve(Intent intent, List<ResolveInfo> matches)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      */
hasResolve(Action action)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      */
getDescription(Action action)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      */
getIcon(Action action)201     public Drawable getIcon(Action action) {
202         return getEntry(action).icon;
203     }
204 
clear()205     public void clear() {
206         mCache.clear();
207     }
208 }
209