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 Lehmann 29e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.contacts.util.PhoneCapabilityTester; 300a49afa2ad697307cc04ef4cb86570574fa720f2Gary Mai 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", 604e3c73fded87101d1bf809d307eff5806e254829Tingting Wang "org.chromium.webview_shell", 6198103e115089f7339d103276dccb4984ea89fc84Daniel Lehmann "com.google.android.browser", 62af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann "com.android.browser"); 63af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 648359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov private final Context mContext; 65af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann private final PackageManager mPackageManager; 66af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 67af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann private static ResolveCache sInstance; 68af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 69af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann /** 70af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Returns an instance of the ResolveCache. Only one internal instance is kept, so 71af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * the argument packageManagers is ignored for all but the first call 72af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 738359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov public synchronized static ResolveCache getInstance(Context context) { 74af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (sInstance == null) { 7590921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann final Context applicationContext = context.getApplicationContext(); 7690921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann sInstance = new ResolveCache(applicationContext); 7790921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann 7890921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann // Register for package-changes so that we can flush our cache 7990921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 8090921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann filter.addAction(Intent.ACTION_PACKAGE_REPLACED); 8190921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 8290921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 8390921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann filter.addDataScheme("package"); 8490921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann applicationContext.registerReceiver(sInstance.mPackageIntentReceiver, filter); 85af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 86af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann return sInstance; 87af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 88af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 8990921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann private synchronized static void flush() { 90af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann sInstance = null; 91af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 92af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 93af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann /** 9490921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann * Called anytime a package is installed, uninstalled etc, so that we can wipe our cache 9590921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann */ 9690921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann private BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() { 9790921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann @Override 9890921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann public void onReceive(Context context, Intent intent) { 9990921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann flush(); 10090921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann } 10190921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann }; 10290921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann 10390921b3bfd9b3492a19a5fbbf6e0309b97a32425Daniel Lehmann /** 104af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Cached entry holding the best {@link ResolveInfo} for a specific 105af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * MIME-type, along with a {@link SoftReference} to its icon. 106af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 107af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann private static class Entry { 108af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann public ResolveInfo bestResolve; 109af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann public Drawable icon; 110af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 111af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 112af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann private HashMap<String, Entry> mCache = new HashMap<String, Entry>(); 113af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 1148359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov 1158359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov private ResolveCache(Context context) { 1168359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov mContext = context; 1178359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov mPackageManager = context.getPackageManager(); 118af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 119af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 120af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann /** 121eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos * Get the {@link Entry} best associated with the given mimetype and intent, 122af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * or create and populate a new one if it doesn't exist. 123af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 124eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos protected Entry getEntry(String mimeType, Intent intent) { 125af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann Entry entry = mCache.get(mimeType); 126af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (entry != null) return entry; 127af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann entry = new Entry(); 128af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 1298359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType) 1308359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov && !PhoneCapabilityTester.isSipPhone(mContext)) { 1318359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov intent = null; 1328359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov } 1338359d04d075243442e973d2c3fdab8d47eb4cbb0Dmitri Plotnikov 134af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (intent != null) { 135af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final List<ResolveInfo> matches = mPackageManager.queryIntentActivities(intent, 136af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann PackageManager.MATCH_DEFAULT_ONLY); 137af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 138af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann // Pick first match, otherwise best found 139af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann ResolveInfo bestResolve = null; 140af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final int size = matches.size(); 141af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (size == 1) { 142af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann bestResolve = matches.get(0); 143af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } else if (size > 1) { 144af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann bestResolve = getBestResolve(intent, matches); 145af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 146af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 147af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (bestResolve != null) { 148af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final Drawable icon = bestResolve.loadIcon(mPackageManager); 149af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 150af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann entry.bestResolve = bestResolve; 151af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann entry.icon = icon; 152af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 153af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 154af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 155af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann mCache.put(mimeType, entry); 156af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann return entry; 157af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 158af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 159af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann /** 160af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Best {@link ResolveInfo} when multiple found. Ties are broken by 161edb576aab33efff623691a89ace3c76cb2ff12d1Daniel Lehmann * selecting first from the {@link QuickContactActivity#sPreferResolve} list of 162af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * preferred packages, second by apps that live on the system partition, 163af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * otherwise the app from the top of the list. This is 164af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * <strong>only</strong> used for selecting a default icon for 165af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * displaying in the track, and does not shortcut the system 166af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * {@link Intent} disambiguation dialog. 167af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 168af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann protected ResolveInfo getBestResolve(Intent intent, List<ResolveInfo> matches) { 169af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann // Try finding preferred activity, otherwise detect disambig 170af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final ResolveInfo foundResolve = mPackageManager.resolveActivity(intent, 171af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann PackageManager.MATCH_DEFAULT_ONLY); 172af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final boolean foundDisambig = (foundResolve.match & 173af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann IntentFilter.MATCH_CATEGORY_MASK) == 0; 174af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 175af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (!foundDisambig) { 176af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann // Found concrete match, so return directly 177af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann return foundResolve; 178af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 179af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 180af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann // Accept any package from prefer list, otherwise first system app 181af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann ResolveInfo firstSystem = null; 182af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann for (ResolveInfo info : matches) { 183af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final boolean isSystem = (info.activityInfo.applicationInfo.flags 184af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann & ApplicationInfo.FLAG_SYSTEM) != 0; 185af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann final boolean isPrefer = sPreferResolve 186af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann .contains(info.activityInfo.applicationInfo.packageName); 187af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 188af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (isPrefer) return info; 189af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann if (isSystem && firstSystem == null) firstSystem = info; 190af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 191af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 192af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann // Return first system found, otherwise first from list 193af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann return firstSystem != null ? firstSystem : matches.get(0); 194af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 195af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 196af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann /** 197af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Check {@link PackageManager} to see if any apps offer to handle the 198eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos * given {@link Intent}. 199af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 200eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos public boolean hasResolve(String mimeType, Intent intent) { 201eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos return getEntry(mimeType, intent).bestResolve != null; 202af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 203af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 204af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann /** 205af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * Return the best icon for the given {@link Action}, which is usually 206af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * based on the {@link ResolveInfo} found through a 207af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann * {@link PackageManager} query. 208af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann */ 209eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos public Drawable getIcon(String mimeType, Intent intent) { 210eb64a4b5c51b39fe56ba4ef97dfff73fdcdf8c75Paul Soulos return getEntry(mimeType, intent).icon; 211af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 212af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann 213af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann public void clear() { 214af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann mCache.clear(); 215af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann } 216af8e3864a2d0131f72337165c846fe909a099e52Daniel Lehmann} 217