blob: d2f6e0918e81d03865f077ab35418ae49bc2a427 [file] [log] [blame]
Evan Millare49dfac2009-06-30 15:21:47 -07001/*
2 * Copyright (C) 2009 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;
18
19import java.io.IOException;
20import java.util.HashMap;
21import java.util.Iterator;
22import java.util.WeakHashMap;
23
24import org.xmlpull.v1.XmlPullParser;
25import org.xmlpull.v1.XmlPullParserException;
26
27import android.content.BroadcastReceiver;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.pm.ApplicationInfo;
32import android.content.pm.PackageManager;
33import android.content.pm.PackageManager.NameNotFoundException;
34import android.content.res.TypedArray;
35import android.graphics.Bitmap;
36import android.graphics.BitmapFactory;
37import android.util.AttributeSet;
38import android.util.Log;
39import android.util.Xml;
40
41
42public final class StyleManager extends BroadcastReceiver {
43
44 public static final String TAG = "StyleManager";
45
46 private static StyleManager sInstance = null;
47
48 private WeakHashMap<String, Bitmap> mIconCache;
49 private HashMap<String, StyleSet> mStyleSetCache;
50
51 /*package*/ static final String DEFAULT_MIMETYPE = "default-icon";
52 private static final String ICON_SET_META_DATA = "com.android.contacts.iconset";
53 private static final String TAG_ICON_SET = "icon-set";
54 private static final String TAG_ICON = "icon";
55 private static final String TAG_ICON_DEFAULT = "icon-default";
56 private static final String KEY_JOIN_CHAR = "|";
57
Evan Millare49dfac2009-06-30 15:21:47 -070058 private StyleManager(Context context) {
59 mIconCache = new WeakHashMap<String, Bitmap>();
60 mStyleSetCache = new HashMap<String, StyleSet>();
Evan Millar4c0864d2009-07-15 11:36:32 -070061 registerIntentReceivers(context);
Evan Millare49dfac2009-06-30 15:21:47 -070062 }
63
64 /**
65 * Returns an instance of StyleManager. This method enforces that only a single instance of this
66 * class exists at any one time in a process.
67 *
68 * @param context A context object
69 * @return StyleManager object
70 */
71 public static StyleManager getInstance(Context context) {
72 if (sInstance == null) {
73 sInstance = new StyleManager(context);
74 }
75 return sInstance;
76 }
77
Evan Millar4c0864d2009-07-15 11:36:32 -070078 private void registerIntentReceivers(Context context) {
Evan Millare49dfac2009-06-30 15:21:47 -070079 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
80 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
81 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
82 filter.addDataScheme("package");
Evan Millar7911ff52009-07-21 15:55:18 -070083
84 // We use getApplicationContext() so that the broadcast reciever can stay registered for
85 // the length of the application lifetime (instead of the calling activity's lifetime).
86 // This is so that we can notified of package changes, and purge the cache accordingly,
87 // but not be woken up if the application process isn't already running, since we will
88 // have no cache to clear at that point.
89 context.getApplicationContext().registerReceiver(this, filter);
Evan Millare49dfac2009-06-30 15:21:47 -070090 }
91
92 @Override
93 public void onReceive(Context context, Intent intent) {
94 final String action = intent.getAction();
95 final String packageName = intent.getData().getSchemeSpecificPart();
96
97 if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
98 || Intent.ACTION_PACKAGE_ADDED.equals(action)
99 || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
100 onPackageChange(packageName);
101 }
102 }
103
104 public void onPackageChange(String packageName) {
105 Iterator<String> itr;
106
107 // Remove cached icons for this package
108 for (itr = mIconCache.keySet().iterator(); itr.hasNext(); ) {
109 if (itr.next().startsWith(packageName + KEY_JOIN_CHAR)) {
110 itr.remove();
111 }
112 }
113
114 // Remove the cached style set for this package
115 mStyleSetCache.remove(packageName);
116 }
117
118 /**
119 * Get the default icon for a given package. If no icon is specified for that package
120 * null is returned.
121 *
122 * @param packageName
123 * @return Bitmap holding the default icon.
124 */
Evan Millar4c0864d2009-07-15 11:36:32 -0700125 public Bitmap getDefaultIcon(Context context, String packageName) {
126 return getMimetypeIcon(context, packageName, DEFAULT_MIMETYPE);
Evan Millare49dfac2009-06-30 15:21:47 -0700127 }
128
129 /**
130 * Get the icon associated with a mimetype for a given package. If no icon is specified for that
131 * package null is returned.
132 *
133 * @param packageName
134 * @return Bitmap holding the default icon.
135 */
Evan Millar4c0864d2009-07-15 11:36:32 -0700136 public Bitmap getMimetypeIcon(Context context, String packageName, String mimetype) {
Evan Millare49dfac2009-06-30 15:21:47 -0700137 String key = getKey(packageName, mimetype);
Evan Millare49dfac2009-06-30 15:21:47 -0700138
Evan Millar7e3b8442009-07-02 10:16:29 -0700139 synchronized(mIconCache) {
140 if (!mIconCache.containsKey(key)) {
141 // Cache miss
142
143 // loadIcon() may return null, which is fine since, if no icon was found we want to
144 // store a null value so we know not to look next time.
Evan Millar4c0864d2009-07-15 11:36:32 -0700145 mIconCache.put(key, loadIcon(context, packageName, mimetype));
Evan Millar7e3b8442009-07-02 10:16:29 -0700146 }
147 return mIconCache.get(key);
Evan Millare49dfac2009-06-30 15:21:47 -0700148 }
Evan Millare49dfac2009-06-30 15:21:47 -0700149 }
150
Evan Millar4c0864d2009-07-15 11:36:32 -0700151 private Bitmap loadIcon(Context context, String packageName, String mimetype) {
Evan Millare49dfac2009-06-30 15:21:47 -0700152 StyleSet ss = null;
153
Evan Millar7e3b8442009-07-02 10:16:29 -0700154 synchronized(mStyleSetCache) {
155 if (!mStyleSetCache.containsKey(packageName)) {
156 // Cache miss
157 try {
Evan Millar4c0864d2009-07-15 11:36:32 -0700158 StyleSet inflated = inflateStyleSet(context, packageName);
Evan Millar7e3b8442009-07-02 10:16:29 -0700159 mStyleSetCache.put(packageName, inflated);
160 } catch (InflateException e) {
161 // If inflation failed keep a null entry so we know not to try again.
162 Log.w(TAG, "Inflation failed: " + e);
163 mStyleSetCache.put(packageName, null);
164 }
Evan Millare49dfac2009-06-30 15:21:47 -0700165 }
166 }
167
168 ss = mStyleSetCache.get(packageName);
169 if (ss == null) {
170 return null;
171 }
172
173 int iconRes;
174 if ((iconRes = ss.getIconRes(mimetype)) == -1) {
175 return null;
176 }
177
Evan Millar4c0864d2009-07-15 11:36:32 -0700178 return BitmapFactory.decodeResource(context.getResources(),
Evan Millare49dfac2009-06-30 15:21:47 -0700179 iconRes, null);
180 }
181
Evan Millar4c0864d2009-07-15 11:36:32 -0700182 private StyleSet inflateStyleSet(Context context, String packageName) throws InflateException {
183 final PackageManager pm = context.getPackageManager();
Evan Millare49dfac2009-06-30 15:21:47 -0700184 final ApplicationInfo ai;
185
186 try {
187 ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
188 } catch (NameNotFoundException e) {
189 return null;
190 }
191
192 XmlPullParser parser = ai.loadXmlMetaData(pm, ICON_SET_META_DATA);
193 final AttributeSet attrs = Xml.asAttributeSet(parser);
194
195 if (parser == null) {
196 return null;
197 }
198
199 try {
200 int type;
201 while ((type = parser.next()) != XmlPullParser.START_TAG
202 && type != XmlPullParser.END_DOCUMENT) {
203 // Drain comments and whitespace
204 }
205
206 if (type != XmlPullParser.START_TAG) {
207 throw new InflateException("No start tag found");
208 }
209
210 if (!TAG_ICON_SET.equals(parser.getName())) {
211 throw new InflateException("Top level element must be StyleSet");
212 }
213
214 // Parse all children actions
215 StyleSet styleSet = new StyleSet();
216 final int depth = parser.getDepth();
217 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
218 && type != XmlPullParser.END_DOCUMENT) {
219 if (type == XmlPullParser.END_TAG) {
220 continue;
221 }
222
223 TypedArray a;
224
225 String mimetype;
226 if (TAG_ICON.equals(parser.getName())) {
Evan Millar4c0864d2009-07-15 11:36:32 -0700227 a = context.obtainStyledAttributes(attrs, android.R.styleable.Icon);
Evan Millare49dfac2009-06-30 15:21:47 -0700228 mimetype = a.getString(com.android.internal.R.styleable.Icon_mimeType);
229 if (mimetype != null) {
230 styleSet.addIcon(mimetype,
231 a.getResourceId(com.android.internal.R.styleable.Icon_icon, -1));
232 }
233 } else if (TAG_ICON_DEFAULT.equals(parser.getName())) {
Evan Millar4c0864d2009-07-15 11:36:32 -0700234 a = context.obtainStyledAttributes(attrs, android.R.styleable.IconDefault);
Evan Millare49dfac2009-06-30 15:21:47 -0700235 styleSet.addIcon(DEFAULT_MIMETYPE,
236 a.getResourceId(
237 com.android.internal.R.styleable.IconDefault_icon, -1));
238 } else {
239 throw new InflateException("Expected " + TAG_ICON + " or "
240 + TAG_ICON_DEFAULT + " tag");
241 }
242 }
243 return styleSet;
244
245 } catch (XmlPullParserException e) {
246 throw new InflateException("Problem reading XML", e);
247 } catch (IOException e) {
248 throw new InflateException("Problem reading XML", e);
249 }
250 }
251
252 private String getKey(String packageName, String mimetype) {
253 return packageName + KEY_JOIN_CHAR + mimetype;
254 }
255
256 public static class InflateException extends Exception {
257 public InflateException(String message) {
258 super(message);
259 }
260
261 public InflateException(String message, Throwable throwable) {
262 super(message, throwable);
263 }
264 }
265
266 private static class StyleSet {
267 private HashMap<String, Integer> mMimetypeIconResMap;
268
269 public StyleSet() {
270 mMimetypeIconResMap = new HashMap<String, Integer>();
271 }
272
273 public int getIconRes(String mimetype) {
274 if (!mMimetypeIconResMap.containsKey(mimetype)) {
275 return -1;
276 }
277 return mMimetypeIconResMap.get(mimetype);
278 }
279
280 public void addIcon(String mimetype, int res) {
281 if (mimetype == null) {
282 return;
283 }
284 mMimetypeIconResMap.put(mimetype, res);
285 }
286 }
287
288 //-------------------------------------------//
289 //-- Methods strictly for testing purposes --//
290 //-------------------------------------------//
291
292 /*package*/ int getIconCacheSize() {
293 return mIconCache.size();
294 }
295
296 /*package*/ int getStyleSetCacheSize() {
297 return mStyleSetCache.size();
298 }
299
300 /*package*/ boolean isStyleSetCacheHit(String packageName) {
301 return mStyleSetCache.containsKey(packageName);
302 }
303
304 /*package*/ boolean isIconCacheHit(String packageName, String mimetype) {
305 return mIconCache.containsKey(getKey(packageName, mimetype));
306 }
307
308 //-------------------------------------------//
309}