blob: 92c4797968a01e3617ef88ebff468317aae0d385 [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
58 private Context mContext;
59
60 private StyleManager(Context context) {
61 mIconCache = new WeakHashMap<String, Bitmap>();
62 mStyleSetCache = new HashMap<String, StyleSet>();
63 mContext = context;
64 registerIntentReceivers();
65 }
66
67 /**
68 * Returns an instance of StyleManager. This method enforces that only a single instance of this
69 * class exists at any one time in a process.
70 *
71 * @param context A context object
72 * @return StyleManager object
73 */
74 public static StyleManager getInstance(Context context) {
75 if (sInstance == null) {
76 sInstance = new StyleManager(context);
77 }
78 return sInstance;
79 }
80
81 private void registerIntentReceivers() {
82 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
83 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
84 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
85 filter.addDataScheme("package");
86 mContext.registerReceiver(this, filter);
87 }
88
89 @Override
90 public void onReceive(Context context, Intent intent) {
91 final String action = intent.getAction();
92 final String packageName = intent.getData().getSchemeSpecificPart();
93
94 if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
95 || Intent.ACTION_PACKAGE_ADDED.equals(action)
96 || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
97 onPackageChange(packageName);
98 }
99 }
100
101 public void onPackageChange(String packageName) {
102 Iterator<String> itr;
103
104 // Remove cached icons for this package
105 for (itr = mIconCache.keySet().iterator(); itr.hasNext(); ) {
106 if (itr.next().startsWith(packageName + KEY_JOIN_CHAR)) {
107 itr.remove();
108 }
109 }
110
111 // Remove the cached style set for this package
112 mStyleSetCache.remove(packageName);
113 }
114
115 /**
116 * Get the default icon for a given package. If no icon is specified for that package
117 * null is returned.
118 *
119 * @param packageName
120 * @return Bitmap holding the default icon.
121 */
122 public Bitmap getDefaultIcon(String packageName) {
123 return getMimetypeIcon(packageName, DEFAULT_MIMETYPE);
124 }
125
126 /**
127 * Get the icon associated with a mimetype for a given package. If no icon is specified for that
128 * package null is returned.
129 *
130 * @param packageName
131 * @return Bitmap holding the default icon.
132 */
133 public Bitmap getMimetypeIcon(String packageName, String mimetype) {
134 String key = getKey(packageName, mimetype);
Evan Millare49dfac2009-06-30 15:21:47 -0700135
Evan Millar7e3b8442009-07-02 10:16:29 -0700136 synchronized(mIconCache) {
137 if (!mIconCache.containsKey(key)) {
138 // Cache miss
139
140 // loadIcon() may return null, which is fine since, if no icon was found we want to
141 // store a null value so we know not to look next time.
142 mIconCache.put(key, loadIcon(packageName, mimetype));
143 }
144 return mIconCache.get(key);
Evan Millare49dfac2009-06-30 15:21:47 -0700145 }
Evan Millare49dfac2009-06-30 15:21:47 -0700146 }
147
148 private Bitmap loadIcon(String packageName, String mimetype) {
149 StyleSet ss = null;
150
Evan Millar7e3b8442009-07-02 10:16:29 -0700151 synchronized(mStyleSetCache) {
152 if (!mStyleSetCache.containsKey(packageName)) {
153 // Cache miss
154 try {
155 StyleSet inflated = inflateStyleSet(packageName);
156 mStyleSetCache.put(packageName, inflated);
157 } catch (InflateException e) {
158 // If inflation failed keep a null entry so we know not to try again.
159 Log.w(TAG, "Inflation failed: " + e);
160 mStyleSetCache.put(packageName, null);
161 }
Evan Millare49dfac2009-06-30 15:21:47 -0700162 }
163 }
164
165 ss = mStyleSetCache.get(packageName);
166 if (ss == null) {
167 return null;
168 }
169
170 int iconRes;
171 if ((iconRes = ss.getIconRes(mimetype)) == -1) {
172 return null;
173 }
174
175 return BitmapFactory.decodeResource(mContext.getResources(),
176 iconRes, null);
177 }
178
179 private StyleSet inflateStyleSet(String packageName) throws InflateException {
180 final PackageManager pm = mContext.getPackageManager();
181 final ApplicationInfo ai;
182
183 try {
184 ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
185 } catch (NameNotFoundException e) {
186 return null;
187 }
188
189 XmlPullParser parser = ai.loadXmlMetaData(pm, ICON_SET_META_DATA);
190 final AttributeSet attrs = Xml.asAttributeSet(parser);
191
192 if (parser == null) {
193 return null;
194 }
195
196 try {
197 int type;
198 while ((type = parser.next()) != XmlPullParser.START_TAG
199 && type != XmlPullParser.END_DOCUMENT) {
200 // Drain comments and whitespace
201 }
202
203 if (type != XmlPullParser.START_TAG) {
204 throw new InflateException("No start tag found");
205 }
206
207 if (!TAG_ICON_SET.equals(parser.getName())) {
208 throw new InflateException("Top level element must be StyleSet");
209 }
210
211 // Parse all children actions
212 StyleSet styleSet = new StyleSet();
213 final int depth = parser.getDepth();
214 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
215 && type != XmlPullParser.END_DOCUMENT) {
216 if (type == XmlPullParser.END_TAG) {
217 continue;
218 }
219
220 TypedArray a;
221
222 String mimetype;
223 if (TAG_ICON.equals(parser.getName())) {
224 a = mContext.obtainStyledAttributes(attrs, android.R.styleable.Icon);
225 mimetype = a.getString(com.android.internal.R.styleable.Icon_mimeType);
226 if (mimetype != null) {
227 styleSet.addIcon(mimetype,
228 a.getResourceId(com.android.internal.R.styleable.Icon_icon, -1));
229 }
230 } else if (TAG_ICON_DEFAULT.equals(parser.getName())) {
231 a = mContext.obtainStyledAttributes(attrs, android.R.styleable.IconDefault);
232 styleSet.addIcon(DEFAULT_MIMETYPE,
233 a.getResourceId(
234 com.android.internal.R.styleable.IconDefault_icon, -1));
235 } else {
236 throw new InflateException("Expected " + TAG_ICON + " or "
237 + TAG_ICON_DEFAULT + " tag");
238 }
239 }
240 return styleSet;
241
242 } catch (XmlPullParserException e) {
243 throw new InflateException("Problem reading XML", e);
244 } catch (IOException e) {
245 throw new InflateException("Problem reading XML", e);
246 }
247 }
248
249 private String getKey(String packageName, String mimetype) {
250 return packageName + KEY_JOIN_CHAR + mimetype;
251 }
252
253 public static class InflateException extends Exception {
254 public InflateException(String message) {
255 super(message);
256 }
257
258 public InflateException(String message, Throwable throwable) {
259 super(message, throwable);
260 }
261 }
262
263 private static class StyleSet {
264 private HashMap<String, Integer> mMimetypeIconResMap;
265
266 public StyleSet() {
267 mMimetypeIconResMap = new HashMap<String, Integer>();
268 }
269
270 public int getIconRes(String mimetype) {
271 if (!mMimetypeIconResMap.containsKey(mimetype)) {
272 return -1;
273 }
274 return mMimetypeIconResMap.get(mimetype);
275 }
276
277 public void addIcon(String mimetype, int res) {
278 if (mimetype == null) {
279 return;
280 }
281 mMimetypeIconResMap.put(mimetype, res);
282 }
283 }
284
285 //-------------------------------------------//
286 //-- Methods strictly for testing purposes --//
287 //-------------------------------------------//
288
289 /*package*/ int getIconCacheSize() {
290 return mIconCache.size();
291 }
292
293 /*package*/ int getStyleSetCacheSize() {
294 return mStyleSetCache.size();
295 }
296
297 /*package*/ boolean isStyleSetCacheHit(String packageName) {
298 return mStyleSetCache.containsKey(packageName);
299 }
300
301 /*package*/ boolean isIconCacheHit(String packageName, String mimetype) {
302 return mIconCache.containsKey(getKey(packageName, mimetype));
303 }
304
305 //-------------------------------------------//
306}