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