1af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann/* 2af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Copyright (C) 2010 The Android Open Source Project 3af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * 4af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Licensed under the Apache License, Version 2.0 (the "License"); 5af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * you may not use this file except in compliance with the License. 6af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * You may obtain a copy of the License at 7af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * 8af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * http://www.apache.org/licenses/LICENSE-2.0 9af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * 10af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Unless required by applicable law or agreed to in writing, software 11af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * distributed under the License is distributed on an "AS IS" BASIS, 12af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * See the License for the specific language governing permissions and 14af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * limitations under the License. 15af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 16af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 17af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmannpackage com.android.contacts.quickcontact; 18af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 1990921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmannimport android.content.BroadcastReceiver; 208359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikovimport android.content.Context; 21af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmannimport android.content.Intent; 22af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmannimport android.content.IntentFilter; 23af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmannimport android.content.pm.ApplicationInfo; 24af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmannimport android.content.pm.PackageManager; 25af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmannimport android.content.pm.ResolveInfo; 26af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmannimport android.graphics.drawable.Drawable; 278359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.SipAddress; 28af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmannimport android.text.TextUtils; 29af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 30e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.contacts.util.PhoneCapabilityTester; 31e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Sets; 32e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Cheng 33af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmannimport java.lang.ref.SoftReference; 34af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmannimport java.util.HashMap; 35af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmannimport java.util.HashSet; 36af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmannimport java.util.List; 37af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 38af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann/** 39af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Internally hold a cache of scaled icons based on {@link PackageManager} 40af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * queries, keyed internally on MIME-type. 41af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 42af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmannpublic class ResolveCache { 43af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann /** 44af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Specific list {@link ApplicationInfo#packageName} of apps that are 45af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * prefered <strong>only</strong> for the purposes of default icons when 46af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * multiple {@link ResolveInfo} are found to match. This only happens when 47af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * the user has not selected a default app yet, and they will still be 48af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * presented with the system disambiguation dialog. 4998103e115089f7339d103276dccb4984ea89fc84Daniel Lehmann * If several of this list match (e.g. Android Browser vs. Chrome), we will pick either one 50af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 51af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann private static final HashSet<String> sPreferResolve = Sets.newHashSet( 52af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann "com.android.email", 5398103e115089f7339d103276dccb4984ea89fc84Daniel Lehmann "com.google.android.email", 5498103e115089f7339d103276dccb4984ea89fc84Daniel Lehmann 55af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann "com.android.phone", 5698103e115089f7339d103276dccb4984ea89fc84Daniel Lehmann 5798103e115089f7339d103276dccb4984ea89fc84Daniel Lehmann "com.google.android.apps.maps", 5898103e115089f7339d103276dccb4984ea89fc84Daniel Lehmann 5998103e115089f7339d103276dccb4984ea89fc84Daniel Lehmann "com.android.chrome", 6098103e115089f7339d103276dccb4984ea89fc84Daniel Lehmann "com.google.android.browser", 61af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann "com.android.browser"); 62af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 638359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov private final Context mContext; 64af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann private final PackageManager mPackageManager; 65af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 66af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann private static ResolveCache sInstance; 67af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 68af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann /** 69af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Returns an instance of the ResolveCache. Only one internal instance is kept, so 70af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * the argument packageManagers is ignored for all but the first call 71af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 728359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov public synchronized static ResolveCache getInstance(Context context) { 73af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (sInstance == null) { 7490921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann final Context applicationContext = context.getApplicationContext(); 7590921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann sInstance = new ResolveCache(applicationContext); 7690921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann 7790921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann // Register for package-changes so that we can flush our cache 7890921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 7990921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann filter.addAction(Intent.ACTION_PACKAGE_REPLACED); 8090921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 8190921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 8290921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann filter.addDataScheme("package"); 8390921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann applicationContext.registerReceiver(sInstance.mPackageIntentReceiver, filter); 84af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 85af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann return sInstance; 86af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 87af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 8890921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann private synchronized static void flush() { 89af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann sInstance = null; 90af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 91af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 92af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann /** 9390921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann * Called anytime a package is installed, uninstalled etc, so that we can wipe our cache 9490921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann */ 9590921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann private BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() { 9690921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann @Override 9790921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann public void onReceive(Context context, Intent intent) { 9890921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann flush(); 9990921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann } 10090921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann }; 10190921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann 10290921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann /** 103af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Cached entry holding the best {@link ResolveInfo} for a specific 104af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * MIME-type, along with a {@link SoftReference} to its icon. 105af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 106af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann private static class Entry { 107af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann public ResolveInfo bestResolve; 108af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann public Drawable icon; 109af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 110af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 111af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann private HashMap<String, Entry> mCache = new HashMap<String, Entry>(); 112af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 1138359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov 1148359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov private ResolveCache(Context context) { 1158359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov mContext = context; 1168359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov mPackageManager = context.getPackageManager(); 117af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 118af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 119af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann /** 120eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos * Get the {@link Entry} best associated with the given mimetype and intent, 121af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * or create and populate a new one if it doesn't exist. 122af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 123eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos protected Entry getEntry(String mimeType, Intent intent) { 124af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann Entry entry = mCache.get(mimeType); 125af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (entry != null) return entry; 126af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann entry = new Entry(); 127af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 1288359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType) 1298359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov && !PhoneCapabilityTester.isSipPhone(mContext)) { 1308359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov intent = null; 1318359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov } 1328359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov 133af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (intent != null) { 134af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final List<ResolveInfo> matches = mPackageManager.queryIntentActivities(intent, 135af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann PackageManager.MATCH_DEFAULT_ONLY); 136af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 137af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann // Pick first match, otherwise best found 138af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann ResolveInfo bestResolve = null; 139af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final int size = matches.size(); 140af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (size == 1) { 141af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann bestResolve = matches.get(0); 142af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } else if (size > 1) { 143af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann bestResolve = getBestResolve(intent, matches); 144af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 145af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 146af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (bestResolve != null) { 147af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final Drawable icon = bestResolve.loadIcon(mPackageManager); 148af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 149af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann entry.bestResolve = bestResolve; 150af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann entry.icon = icon; 151af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 152af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 153af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 154af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann mCache.put(mimeType, entry); 155af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann return entry; 156af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 157af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 158af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann /** 159af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Best {@link ResolveInfo} when multiple found. Ties are broken by 160edb576aab33efff623691a89ace3c76cb2ff12d1Daniel Lehmann * selecting first from the {@link QuickContactActivity#sPreferResolve} list of 161af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * preferred packages, second by apps that live on the system partition, 162af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * otherwise the app from the top of the list. This is 163af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * <strong>only</strong> used for selecting a default icon for 164af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * displaying in the track, and does not shortcut the system 165af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * {@link Intent} disambiguation dialog. 166af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 167af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann protected ResolveInfo getBestResolve(Intent intent, List<ResolveInfo> matches) { 168af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann // Try finding preferred activity, otherwise detect disambig 169af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final ResolveInfo foundResolve = mPackageManager.resolveActivity(intent, 170af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann PackageManager.MATCH_DEFAULT_ONLY); 171af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final boolean foundDisambig = (foundResolve.match & 172af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann IntentFilter.MATCH_CATEGORY_MASK) == 0; 173af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 174af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (!foundDisambig) { 175af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann // Found concrete match, so return directly 176af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann return foundResolve; 177af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 178af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 179af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann // Accept any package from prefer list, otherwise first system app 180af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann ResolveInfo firstSystem = null; 181af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann for (ResolveInfo info : matches) { 182af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final boolean isSystem = (info.activityInfo.applicationInfo.flags 183af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann & ApplicationInfo.FLAG_SYSTEM) != 0; 184af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final boolean isPrefer = sPreferResolve 185af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann .contains(info.activityInfo.applicationInfo.packageName); 186af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 187af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (isPrefer) return info; 188af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (isSystem && firstSystem == null) firstSystem = info; 189af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 190af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 191af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann // Return first system found, otherwise first from list 192af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann return firstSystem != null ? firstSystem : matches.get(0); 193af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 194af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 195af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann /** 196af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Check {@link PackageManager} to see if any apps offer to handle the 197eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos * given {@link Intent}. 198af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 199eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos public boolean hasResolve(String mimeType, Intent intent) { 200eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos return getEntry(mimeType, intent).bestResolve != null; 201af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 202af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 203af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann /** 204af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Return the best icon for the given {@link Action}, which is usually 205af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * based on the {@link ResolveInfo} found through a 206af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * {@link PackageManager} query. 207af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 208eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos public Drawable getIcon(String mimeType, Intent intent) { 209eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos return getEntry(mimeType, intent).icon; 210af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 211af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 212af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann public void clear() { 213af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann mCache.clear(); 214af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 215af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann} 216