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