Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 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 | |
Daniel Lehmann | 90921b3 | 2011-10-16 17:58:27 -0700 | [diff] [blame] | 19 | import android.content.BroadcastReceiver; |
Dmitri Plotnikov | 8359d04 | 2011-02-16 17:29:36 -0800 | [diff] [blame] | 20 | import android.content.Context; |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 21 | import android.content.Intent; |
| 22 | import android.content.IntentFilter; |
| 23 | import android.content.pm.ApplicationInfo; |
| 24 | import android.content.pm.PackageManager; |
| 25 | import android.content.pm.ResolveInfo; |
| 26 | import android.graphics.drawable.Drawable; |
Dmitri Plotnikov | 8359d04 | 2011-02-16 17:29:36 -0800 | [diff] [blame] | 27 | import android.provider.ContactsContract.CommonDataKinds.SipAddress; |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 28 | import android.text.TextUtils; |
| 29 | |
Chiao Cheng | e0b2f1e | 2012-06-12 13:07:56 -0700 | [diff] [blame] | 30 | import com.android.contacts.util.PhoneCapabilityTester; |
| 31 | import com.google.common.collect.Sets; |
| 32 | |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 33 | import java.lang.ref.SoftReference; |
| 34 | import java.util.HashMap; |
| 35 | import java.util.HashSet; |
| 36 | import 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 | */ |
| 42 | public 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 Lehmann | 98103e1 | 2012-04-25 17:53:31 -0700 | [diff] [blame] | 49 | * If several of this list match (e.g. Android Browser vs. Chrome), we will pick either one |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 50 | */ |
| 51 | private static final HashSet<String> sPreferResolve = Sets.newHashSet( |
| 52 | "com.android.email", |
Daniel Lehmann | 98103e1 | 2012-04-25 17:53:31 -0700 | [diff] [blame] | 53 | "com.google.android.email", |
| 54 | |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 55 | "com.android.phone", |
Daniel Lehmann | 98103e1 | 2012-04-25 17:53:31 -0700 | [diff] [blame] | 56 | |
| 57 | "com.google.android.apps.maps", |
| 58 | |
| 59 | "com.android.chrome", |
Tingting Wang | 4e3c73f | 2015-08-13 17:39:04 -0700 | [diff] [blame] | 60 | "org.chromium.webview_shell", |
Daniel Lehmann | 98103e1 | 2012-04-25 17:53:31 -0700 | [diff] [blame] | 61 | "com.google.android.browser", |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 62 | "com.android.browser"); |
| 63 | |
Dmitri Plotnikov | 8359d04 | 2011-02-16 17:29:36 -0800 | [diff] [blame] | 64 | private final Context mContext; |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 65 | 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 Plotnikov | 8359d04 | 2011-02-16 17:29:36 -0800 | [diff] [blame] | 73 | public synchronized static ResolveCache getInstance(Context context) { |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 74 | if (sInstance == null) { |
Daniel Lehmann | 90921b3 | 2011-10-16 17:58:27 -0700 | [diff] [blame] | 75 | 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 Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 85 | } |
| 86 | return sInstance; |
| 87 | } |
| 88 | |
Daniel Lehmann | 90921b3 | 2011-10-16 17:58:27 -0700 | [diff] [blame] | 89 | private synchronized static void flush() { |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 90 | sInstance = null; |
| 91 | } |
| 92 | |
| 93 | /** |
Daniel Lehmann | 90921b3 | 2011-10-16 17:58:27 -0700 | [diff] [blame] | 94 | * 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 Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 104 | * 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 Plotnikov | 8359d04 | 2011-02-16 17:29:36 -0800 | [diff] [blame] | 114 | |
| 115 | private ResolveCache(Context context) { |
| 116 | mContext = context; |
| 117 | mPackageManager = context.getPackageManager(); |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 118 | } |
| 119 | |
| 120 | /** |
Paul Soulos | eb64a4b | 2014-07-07 17:03:27 -0700 | [diff] [blame] | 121 | * Get the {@link Entry} best associated with the given mimetype and intent, |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 122 | * or create and populate a new one if it doesn't exist. |
| 123 | */ |
Paul Soulos | eb64a4b | 2014-07-07 17:03:27 -0700 | [diff] [blame] | 124 | protected Entry getEntry(String mimeType, Intent intent) { |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 125 | Entry entry = mCache.get(mimeType); |
| 126 | if (entry != null) return entry; |
| 127 | entry = new Entry(); |
| 128 | |
Dmitri Plotnikov | 8359d04 | 2011-02-16 17:29:36 -0800 | [diff] [blame] | 129 | if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType) |
| 130 | && !PhoneCapabilityTester.isSipPhone(mContext)) { |
| 131 | intent = null; |
| 132 | } |
| 133 | |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 134 | 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 Lehmann | edb576a | 2011-07-27 16:45:13 -0700 | [diff] [blame] | 161 | * selecting first from the {@link QuickContactActivity#sPreferResolve} list of |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 162 | * 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 Soulos | eb64a4b | 2014-07-07 17:03:27 -0700 | [diff] [blame] | 198 | * given {@link Intent}. |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 199 | */ |
Paul Soulos | eb64a4b | 2014-07-07 17:03:27 -0700 | [diff] [blame] | 200 | public boolean hasResolve(String mimeType, Intent intent) { |
| 201 | return getEntry(mimeType, intent).bestResolve != null; |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 202 | } |
| 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 Soulos | eb64a4b | 2014-07-07 17:03:27 -0700 | [diff] [blame] | 209 | public Drawable getIcon(String mimeType, Intent intent) { |
| 210 | return getEntry(mimeType, intent).icon; |
Daniel Lehmann | af8e386 | 2010-11-19 15:38:44 -0800 | [diff] [blame] | 211 | } |
| 212 | |
| 213 | public void clear() { |
| 214 | mCache.clear(); |
| 215 | } |
| 216 | } |