blob: e6adaf3534b12803984797df2b46a35809f4c17e [file] [log] [blame]
Daniel Lehmannaf8e3862010-11-19 15:38:44 -08001/*
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
17package com.android.contacts.quickcontact;
18
Daniel Lehmann90921b32011-10-16 17:58:27 -070019import android.content.BroadcastReceiver;
Dmitri Plotnikov8359d042011-02-16 17:29:36 -080020import android.content.Context;
Daniel Lehmannaf8e3862010-11-19 15:38:44 -080021import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.pm.ApplicationInfo;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.graphics.drawable.Drawable;
Dmitri Plotnikov8359d042011-02-16 17:29:36 -080027import android.provider.ContactsContract.CommonDataKinds.SipAddress;
Daniel Lehmannaf8e3862010-11-19 15:38:44 -080028import android.text.TextUtils;
29
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070030import com.android.contacts.util.PhoneCapabilityTester;
31import com.google.common.collect.Sets;
32
Daniel Lehmannaf8e3862010-11-19 15:38:44 -080033import java.lang.ref.SoftReference;
34import java.util.HashMap;
35import java.util.HashSet;
36import java.util.List;
37
38/**
39 * Internally hold a cache of scaled icons based on {@link PackageManager}
40 * queries, keyed internally on MIME-type.
41 */
42public class ResolveCache {
43 /**
44 * Specific list {@link ApplicationInfo#packageName} of apps that are
45 * prefered <strong>only</strong> for the purposes of default icons when
46 * multiple {@link ResolveInfo} are found to match. This only happens when
47 * the user has not selected a default app yet, and they will still be
48 * presented with the system disambiguation dialog.
Daniel Lehmann98103e12012-04-25 17:53:31 -070049 * If several of this list match (e.g. Android Browser vs. Chrome), we will pick either one
Daniel Lehmannaf8e3862010-11-19 15:38:44 -080050 */
51 private static final HashSet<String> sPreferResolve = Sets.newHashSet(
52 "com.android.email",
Daniel Lehmann98103e12012-04-25 17:53:31 -070053 "com.google.android.email",
54
Daniel Lehmannaf8e3862010-11-19 15:38:44 -080055 "com.android.phone",
Daniel Lehmann98103e12012-04-25 17:53:31 -070056
57 "com.google.android.apps.maps",
58
59 "com.android.chrome",
Tingting Wang4e3c73f2015-08-13 17:39:04 -070060 "org.chromium.webview_shell",
Daniel Lehmann98103e12012-04-25 17:53:31 -070061 "com.google.android.browser",
Daniel Lehmannaf8e3862010-11-19 15:38:44 -080062 "com.android.browser");
63
Dmitri Plotnikov8359d042011-02-16 17:29:36 -080064 private final Context mContext;
Daniel Lehmannaf8e3862010-11-19 15:38:44 -080065 private final PackageManager mPackageManager;
66
67 private static ResolveCache sInstance;
68
69 /**
70 * Returns an instance of the ResolveCache. Only one internal instance is kept, so
71 * the argument packageManagers is ignored for all but the first call
72 */
Dmitri Plotnikov8359d042011-02-16 17:29:36 -080073 public synchronized static ResolveCache getInstance(Context context) {
Daniel Lehmannaf8e3862010-11-19 15:38:44 -080074 if (sInstance == null) {
Daniel Lehmann90921b32011-10-16 17:58:27 -070075 final Context applicationContext = context.getApplicationContext();
76 sInstance = new ResolveCache(applicationContext);
77
78 // Register for package-changes so that we can flush our cache
79 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
80 filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
81 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
82 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
83 filter.addDataScheme("package");
84 applicationContext.registerReceiver(sInstance.mPackageIntentReceiver, filter);
Daniel Lehmannaf8e3862010-11-19 15:38:44 -080085 }
86 return sInstance;
87 }
88
Daniel Lehmann90921b32011-10-16 17:58:27 -070089 private synchronized static void flush() {
Daniel Lehmannaf8e3862010-11-19 15:38:44 -080090 sInstance = null;
91 }
92
93 /**
Daniel Lehmann90921b32011-10-16 17:58:27 -070094 * Called anytime a package is installed, uninstalled etc, so that we can wipe our cache
95 */
96 private BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
97 @Override
98 public void onReceive(Context context, Intent intent) {
99 flush();
100 }
101 };
102
103 /**
Daniel Lehmannaf8e3862010-11-19 15:38:44 -0800104 * Cached entry holding the best {@link ResolveInfo} for a specific
105 * MIME-type, along with a {@link SoftReference} to its icon.
106 */
107 private static class Entry {
108 public ResolveInfo bestResolve;
109 public Drawable icon;
110 }
111
112 private HashMap<String, Entry> mCache = new HashMap<String, Entry>();
113
Dmitri Plotnikov8359d042011-02-16 17:29:36 -0800114
115 private ResolveCache(Context context) {
116 mContext = context;
117 mPackageManager = context.getPackageManager();
Daniel Lehmannaf8e3862010-11-19 15:38:44 -0800118 }
119
120 /**
Paul Souloseb64a4b2014-07-07 17:03:27 -0700121 * Get the {@link Entry} best associated with the given mimetype and intent,
Daniel Lehmannaf8e3862010-11-19 15:38:44 -0800122 * or create and populate a new one if it doesn't exist.
123 */
Paul Souloseb64a4b2014-07-07 17:03:27 -0700124 protected Entry getEntry(String mimeType, Intent intent) {
Daniel Lehmannaf8e3862010-11-19 15:38:44 -0800125 Entry entry = mCache.get(mimeType);
126 if (entry != null) return entry;
127 entry = new Entry();
128
Dmitri Plotnikov8359d042011-02-16 17:29:36 -0800129 if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType)
130 && !PhoneCapabilityTester.isSipPhone(mContext)) {
131 intent = null;
132 }
133
Daniel Lehmannaf8e3862010-11-19 15:38:44 -0800134 if (intent != null) {
135 final List<ResolveInfo> matches = mPackageManager.queryIntentActivities(intent,
136 PackageManager.MATCH_DEFAULT_ONLY);
137
138 // Pick first match, otherwise best found
139 ResolveInfo bestResolve = null;
140 final int size = matches.size();
141 if (size == 1) {
142 bestResolve = matches.get(0);
143 } else if (size > 1) {
144 bestResolve = getBestResolve(intent, matches);
145 }
146
147 if (bestResolve != null) {
148 final Drawable icon = bestResolve.loadIcon(mPackageManager);
149
150 entry.bestResolve = bestResolve;
151 entry.icon = icon;
152 }
153 }
154
155 mCache.put(mimeType, entry);
156 return entry;
157 }
158
159 /**
160 * Best {@link ResolveInfo} when multiple found. Ties are broken by
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700161 * selecting first from the {@link QuickContactActivity#sPreferResolve} list of
Daniel Lehmannaf8e3862010-11-19 15:38:44 -0800162 * preferred packages, second by apps that live on the system partition,
163 * otherwise the app from the top of the list. This is
164 * <strong>only</strong> used for selecting a default icon for
165 * displaying in the track, and does not shortcut the system
166 * {@link Intent} disambiguation dialog.
167 */
168 protected ResolveInfo getBestResolve(Intent intent, List<ResolveInfo> matches) {
169 // Try finding preferred activity, otherwise detect disambig
170 final ResolveInfo foundResolve = mPackageManager.resolveActivity(intent,
171 PackageManager.MATCH_DEFAULT_ONLY);
172 final boolean foundDisambig = (foundResolve.match &
173 IntentFilter.MATCH_CATEGORY_MASK) == 0;
174
175 if (!foundDisambig) {
176 // Found concrete match, so return directly
177 return foundResolve;
178 }
179
180 // Accept any package from prefer list, otherwise first system app
181 ResolveInfo firstSystem = null;
182 for (ResolveInfo info : matches) {
183 final boolean isSystem = (info.activityInfo.applicationInfo.flags
184 & ApplicationInfo.FLAG_SYSTEM) != 0;
185 final boolean isPrefer = sPreferResolve
186 .contains(info.activityInfo.applicationInfo.packageName);
187
188 if (isPrefer) return info;
189 if (isSystem && firstSystem == null) firstSystem = info;
190 }
191
192 // Return first system found, otherwise first from list
193 return firstSystem != null ? firstSystem : matches.get(0);
194 }
195
196 /**
197 * Check {@link PackageManager} to see if any apps offer to handle the
Paul Souloseb64a4b2014-07-07 17:03:27 -0700198 * given {@link Intent}.
Daniel Lehmannaf8e3862010-11-19 15:38:44 -0800199 */
Paul Souloseb64a4b2014-07-07 17:03:27 -0700200 public boolean hasResolve(String mimeType, Intent intent) {
201 return getEntry(mimeType, intent).bestResolve != null;
Daniel Lehmannaf8e3862010-11-19 15:38:44 -0800202 }
203
204 /**
205 * Return the best icon for the given {@link Action}, which is usually
206 * based on the {@link ResolveInfo} found through a
207 * {@link PackageManager} query.
208 */
Paul Souloseb64a4b2014-07-07 17:03:27 -0700209 public Drawable getIcon(String mimeType, Intent intent) {
210 return getEntry(mimeType, intent).icon;
Daniel Lehmannaf8e3862010-11-19 15:38:44 -0800211 }
212
213 public void clear() {
214 mCache.clear();
215 }
216}