blob: d14b1c5412600ee74a07b41f02b5464f51137a31 [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 Millar4c0864d2009-07-15 11:36:32 -070083 context.registerReceiver(this, filter);
Evan Millare49dfac2009-06-30 15:21:47 -070084 }
85
86 @Override
87 public void onReceive(Context context, Intent intent) {
88 final String action = intent.getAction();
89 final String packageName = intent.getData().getSchemeSpecificPart();
90
91 if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
92 || Intent.ACTION_PACKAGE_ADDED.equals(action)
93 || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
94 onPackageChange(packageName);
95 }
96 }
97
98 public void onPackageChange(String packageName) {
99 Iterator<String> itr;
100
101 // Remove cached icons for this package
102 for (itr = mIconCache.keySet().iterator(); itr.hasNext(); ) {
103 if (itr.next().startsWith(packageName + KEY_JOIN_CHAR)) {
104 itr.remove();
105 }
106 }
107
108 // Remove the cached style set for this package
109 mStyleSetCache.remove(packageName);
110 }
111
112 /**
113 * Get the default icon for a given package. If no icon is specified for that package
114 * null is returned.
115 *
116 * @param packageName
117 * @return Bitmap holding the default icon.
118 */
Evan Millar4c0864d2009-07-15 11:36:32 -0700119 public Bitmap getDefaultIcon(Context context, String packageName) {
120 return getMimetypeIcon(context, packageName, DEFAULT_MIMETYPE);
Evan Millare49dfac2009-06-30 15:21:47 -0700121 }
122
123 /**
124 * Get the icon associated with a mimetype for a given package. If no icon is specified for that
125 * package null is returned.
126 *
127 * @param packageName
128 * @return Bitmap holding the default icon.
129 */
Evan Millar4c0864d2009-07-15 11:36:32 -0700130 public Bitmap getMimetypeIcon(Context context, String packageName, String mimetype) {
Evan Millare49dfac2009-06-30 15:21:47 -0700131 String key = getKey(packageName, mimetype);
Evan Millare49dfac2009-06-30 15:21:47 -0700132
Evan Millar7e3b8442009-07-02 10:16:29 -0700133 synchronized(mIconCache) {
134 if (!mIconCache.containsKey(key)) {
135 // Cache miss
136
137 // loadIcon() may return null, which is fine since, if no icon was found we want to
138 // store a null value so we know not to look next time.
Evan Millar4c0864d2009-07-15 11:36:32 -0700139 mIconCache.put(key, loadIcon(context, packageName, mimetype));
Evan Millar7e3b8442009-07-02 10:16:29 -0700140 }
141 return mIconCache.get(key);
Evan Millare49dfac2009-06-30 15:21:47 -0700142 }
Evan Millare49dfac2009-06-30 15:21:47 -0700143 }
144
Evan Millar4c0864d2009-07-15 11:36:32 -0700145 private Bitmap loadIcon(Context context, String packageName, String mimetype) {
Evan Millare49dfac2009-06-30 15:21:47 -0700146 StyleSet ss = null;
147
Evan Millar7e3b8442009-07-02 10:16:29 -0700148 synchronized(mStyleSetCache) {
149 if (!mStyleSetCache.containsKey(packageName)) {
150 // Cache miss
151 try {
Evan Millar4c0864d2009-07-15 11:36:32 -0700152 StyleSet inflated = inflateStyleSet(context, packageName);
Evan Millar7e3b8442009-07-02 10:16:29 -0700153 mStyleSetCache.put(packageName, inflated);
154 } catch (InflateException e) {
155 // If inflation failed keep a null entry so we know not to try again.
156 Log.w(TAG, "Inflation failed: " + e);
157 mStyleSetCache.put(packageName, null);
158 }
Evan Millare49dfac2009-06-30 15:21:47 -0700159 }
160 }
161
162 ss = mStyleSetCache.get(packageName);
163 if (ss == null) {
164 return null;
165 }
166
167 int iconRes;
168 if ((iconRes = ss.getIconRes(mimetype)) == -1) {
169 return null;
170 }
171
Evan Millar4c0864d2009-07-15 11:36:32 -0700172 return BitmapFactory.decodeResource(context.getResources(),
Evan Millare49dfac2009-06-30 15:21:47 -0700173 iconRes, null);
174 }
175
Evan Millar4c0864d2009-07-15 11:36:32 -0700176 private StyleSet inflateStyleSet(Context context, String packageName) throws InflateException {
177 final PackageManager pm = context.getPackageManager();
Evan Millare49dfac2009-06-30 15:21:47 -0700178 final ApplicationInfo ai;
179
180 try {
181 ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
182 } catch (NameNotFoundException e) {
183 return null;
184 }
185
186 XmlPullParser parser = ai.loadXmlMetaData(pm, ICON_SET_META_DATA);
187 final AttributeSet attrs = Xml.asAttributeSet(parser);
188
189 if (parser == null) {
190 return null;
191 }
192
193 try {
194 int type;
195 while ((type = parser.next()) != XmlPullParser.START_TAG
196 && type != XmlPullParser.END_DOCUMENT) {
197 // Drain comments and whitespace
198 }
199
200 if (type != XmlPullParser.START_TAG) {
201 throw new InflateException("No start tag found");
202 }
203
204 if (!TAG_ICON_SET.equals(parser.getName())) {
205 throw new InflateException("Top level element must be StyleSet");
206 }
207
208 // Parse all children actions
209 StyleSet styleSet = new StyleSet();
210 final int depth = parser.getDepth();
211 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
212 && type != XmlPullParser.END_DOCUMENT) {
213 if (type == XmlPullParser.END_TAG) {
214 continue;
215 }
216
217 TypedArray a;
218
219 String mimetype;
220 if (TAG_ICON.equals(parser.getName())) {
Evan Millar4c0864d2009-07-15 11:36:32 -0700221 a = context.obtainStyledAttributes(attrs, android.R.styleable.Icon);
Evan Millare49dfac2009-06-30 15:21:47 -0700222 mimetype = a.getString(com.android.internal.R.styleable.Icon_mimeType);
223 if (mimetype != null) {
224 styleSet.addIcon(mimetype,
225 a.getResourceId(com.android.internal.R.styleable.Icon_icon, -1));
226 }
227 } else if (TAG_ICON_DEFAULT.equals(parser.getName())) {
Evan Millar4c0864d2009-07-15 11:36:32 -0700228 a = context.obtainStyledAttributes(attrs, android.R.styleable.IconDefault);
Evan Millare49dfac2009-06-30 15:21:47 -0700229 styleSet.addIcon(DEFAULT_MIMETYPE,
230 a.getResourceId(
231 com.android.internal.R.styleable.IconDefault_icon, -1));
232 } else {
233 throw new InflateException("Expected " + TAG_ICON + " or "
234 + TAG_ICON_DEFAULT + " tag");
235 }
236 }
237 return styleSet;
238
239 } catch (XmlPullParserException e) {
240 throw new InflateException("Problem reading XML", e);
241 } catch (IOException e) {
242 throw new InflateException("Problem reading XML", e);
243 }
244 }
245
246 private String getKey(String packageName, String mimetype) {
247 return packageName + KEY_JOIN_CHAR + mimetype;
248 }
249
250 public static class InflateException extends Exception {
251 public InflateException(String message) {
252 super(message);
253 }
254
255 public InflateException(String message, Throwable throwable) {
256 super(message, throwable);
257 }
258 }
259
260 private static class StyleSet {
261 private HashMap<String, Integer> mMimetypeIconResMap;
262
263 public StyleSet() {
264 mMimetypeIconResMap = new HashMap<String, Integer>();
265 }
266
267 public int getIconRes(String mimetype) {
268 if (!mMimetypeIconResMap.containsKey(mimetype)) {
269 return -1;
270 }
271 return mMimetypeIconResMap.get(mimetype);
272 }
273
274 public void addIcon(String mimetype, int res) {
275 if (mimetype == null) {
276 return;
277 }
278 mMimetypeIconResMap.put(mimetype, res);
279 }
280 }
281
282 //-------------------------------------------//
283 //-- Methods strictly for testing purposes --//
284 //-------------------------------------------//
285
286 /*package*/ int getIconCacheSize() {
287 return mIconCache.size();
288 }
289
290 /*package*/ int getStyleSetCacheSize() {
291 return mStyleSetCache.size();
292 }
293
294 /*package*/ boolean isStyleSetCacheHit(String packageName) {
295 return mStyleSetCache.containsKey(packageName);
296 }
297
298 /*package*/ boolean isIconCacheHit(String packageName, String mimetype) {
299 return mIconCache.containsKey(getKey(packageName, mimetype));
300 }
301
302 //-------------------------------------------//
303}