blob: bd93edd90ce83791f93996fc6533c4ab2a63a9fc [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);
135 if (!mIconCache.containsKey(key)) {
136 // Cache miss
137
138 // loadIcon() may return null, which is fine since, if no icon was found we want to
139 // store a null value so we know not to look next time.
140 mIconCache.put(key, loadIcon(packageName, mimetype));
141 }
142 return mIconCache.get(key);
143 }
144
145 private Bitmap loadIcon(String packageName, String mimetype) {
146 StyleSet ss = null;
147
148 if (!mStyleSetCache.containsKey(packageName)) {
149 // Cache miss
150 try {
151 StyleSet inflated = inflateStyleSet(packageName);
152 mStyleSetCache.put(packageName, inflated);
153 } catch (InflateException e) {
154 // If inflation failed keep a null entry so we know not to try again.
155 Log.w(TAG, "Inflation failed: " + e);
156 mStyleSetCache.put(packageName, null);
157 }
158 }
159
160 ss = mStyleSetCache.get(packageName);
161 if (ss == null) {
162 return null;
163 }
164
165 int iconRes;
166 if ((iconRes = ss.getIconRes(mimetype)) == -1) {
167 return null;
168 }
169
170 return BitmapFactory.decodeResource(mContext.getResources(),
171 iconRes, null);
172 }
173
174 private StyleSet inflateStyleSet(String packageName) throws InflateException {
175 final PackageManager pm = mContext.getPackageManager();
176 final ApplicationInfo ai;
177
178 try {
179 ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
180 } catch (NameNotFoundException e) {
181 return null;
182 }
183
184 XmlPullParser parser = ai.loadXmlMetaData(pm, ICON_SET_META_DATA);
185 final AttributeSet attrs = Xml.asAttributeSet(parser);
186
187 if (parser == null) {
188 return null;
189 }
190
191 try {
192 int type;
193 while ((type = parser.next()) != XmlPullParser.START_TAG
194 && type != XmlPullParser.END_DOCUMENT) {
195 // Drain comments and whitespace
196 }
197
198 if (type != XmlPullParser.START_TAG) {
199 throw new InflateException("No start tag found");
200 }
201
202 if (!TAG_ICON_SET.equals(parser.getName())) {
203 throw new InflateException("Top level element must be StyleSet");
204 }
205
206 // Parse all children actions
207 StyleSet styleSet = new StyleSet();
208 final int depth = parser.getDepth();
209 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
210 && type != XmlPullParser.END_DOCUMENT) {
211 if (type == XmlPullParser.END_TAG) {
212 continue;
213 }
214
215 TypedArray a;
216
217 String mimetype;
218 if (TAG_ICON.equals(parser.getName())) {
219 a = mContext.obtainStyledAttributes(attrs, android.R.styleable.Icon);
220 mimetype = a.getString(com.android.internal.R.styleable.Icon_mimeType);
221 if (mimetype != null) {
222 styleSet.addIcon(mimetype,
223 a.getResourceId(com.android.internal.R.styleable.Icon_icon, -1));
224 }
225 } else if (TAG_ICON_DEFAULT.equals(parser.getName())) {
226 a = mContext.obtainStyledAttributes(attrs, android.R.styleable.IconDefault);
227 styleSet.addIcon(DEFAULT_MIMETYPE,
228 a.getResourceId(
229 com.android.internal.R.styleable.IconDefault_icon, -1));
230 } else {
231 throw new InflateException("Expected " + TAG_ICON + " or "
232 + TAG_ICON_DEFAULT + " tag");
233 }
234 }
235 return styleSet;
236
237 } catch (XmlPullParserException e) {
238 throw new InflateException("Problem reading XML", e);
239 } catch (IOException e) {
240 throw new InflateException("Problem reading XML", e);
241 }
242 }
243
244 private String getKey(String packageName, String mimetype) {
245 return packageName + KEY_JOIN_CHAR + mimetype;
246 }
247
248 public static class InflateException extends Exception {
249 public InflateException(String message) {
250 super(message);
251 }
252
253 public InflateException(String message, Throwable throwable) {
254 super(message, throwable);
255 }
256 }
257
258 private static class StyleSet {
259 private HashMap<String, Integer> mMimetypeIconResMap;
260
261 public StyleSet() {
262 mMimetypeIconResMap = new HashMap<String, Integer>();
263 }
264
265 public int getIconRes(String mimetype) {
266 if (!mMimetypeIconResMap.containsKey(mimetype)) {
267 return -1;
268 }
269 return mMimetypeIconResMap.get(mimetype);
270 }
271
272 public void addIcon(String mimetype, int res) {
273 if (mimetype == null) {
274 return;
275 }
276 mMimetypeIconResMap.put(mimetype, res);
277 }
278 }
279
280 //-------------------------------------------//
281 //-- Methods strictly for testing purposes --//
282 //-------------------------------------------//
283
284 /*package*/ int getIconCacheSize() {
285 return mIconCache.size();
286 }
287
288 /*package*/ int getStyleSetCacheSize() {
289 return mStyleSetCache.size();
290 }
291
292 /*package*/ boolean isStyleSetCacheHit(String packageName) {
293 return mStyleSetCache.containsKey(packageName);
294 }
295
296 /*package*/ boolean isIconCacheHit(String packageName, String mimetype) {
297 return mIconCache.containsKey(getKey(packageName, mimetype));
298 }
299
300 //-------------------------------------------//
301}