blob: 31dc652890c0514bdbbce23d2857ef89e8cf0e1b [file] [log] [blame]
Dmitri Plotnikov06191cd2009-05-07 14:11:52 -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 Sharkey3f177592009-05-18 15:23:12 -070019import com.android.contacts.EdgeTriggerView.EdgeTriggerListener;
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -070020import com.android.contacts.SocialStreamActivity.MappingCache.Mapping;
Jeff Sharkey3f177592009-05-18 15:23:12 -070021import com.android.providers.contacts2.ContactsContract;
22import com.android.providers.contacts2.ContactsContract.Aggregates;
23import com.android.providers.contacts2.ContactsContract.Contacts;
24import com.android.providers.contacts2.ContactsContract.Data;
25import com.android.providers.contacts2.ContactsContract.CommonDataKinds.Photo;
Jeff Sharkey3f177592009-05-18 15:23:12 -070026import com.android.providers.contacts2.SocialContract.Activities;
Dmitri Plotnikov06191cd2009-05-07 14:11:52 -070027
Jeff Sharkey3f177592009-05-18 15:23:12 -070028import org.xmlpull.v1.XmlPullParser;
29import org.xmlpull.v1.XmlPullParserException;
30
31import android.app.ListActivity;
32import android.content.ContentResolver;
33import android.content.ContentUris;
34import android.content.Context;
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -070035import android.content.Intent;
Jeff Sharkey3f177592009-05-18 15:23:12 -070036import android.content.pm.ApplicationInfo;
37import android.content.pm.PackageManager;
38import android.content.pm.PackageManager.NameNotFoundException;
Jeff Sharkey3f177592009-05-18 15:23:12 -070039import android.content.res.TypedArray;
40import android.database.Cursor;
41import android.graphics.Bitmap;
42import android.graphics.BitmapFactory;
43import android.graphics.Canvas;
44import android.graphics.Paint;
45import android.graphics.PaintFlagsDrawFilter;
Jeff Sharkey3f177592009-05-18 15:23:12 -070046import android.graphics.Rect;
Jeff Sharkey3f177592009-05-18 15:23:12 -070047import android.net.Uri;
48import android.os.Bundle;
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -070049import android.text.SpannableStringBuilder;
Jeff Sharkey3f177592009-05-18 15:23:12 -070050import android.text.format.DateUtils;
Dmitri Plotnikov9a41d432009-05-19 18:33:46 -070051import android.text.style.StyleSpan;
Dmitri Plotnikov05f158f2009-05-21 11:37:15 -070052import android.text.style.UnderlineSpan;
Jeff Sharkey3f177592009-05-18 15:23:12 -070053import android.util.AttributeSet;
54import android.util.Log;
55import android.util.Xml;
56import android.view.LayoutInflater;
57import android.view.View;
58import android.view.ViewGroup;
Jeff Sharkey3f177592009-05-18 15:23:12 -070059import android.widget.CursorAdapter;
60import android.widget.ImageView;
61import android.widget.ListAdapter;
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -070062import android.widget.ListView;
Jeff Sharkey3f177592009-05-18 15:23:12 -070063import android.widget.TextView;
64
65import java.io.IOException;
66import java.util.HashMap;
67import java.util.LinkedList;
68import java.util.List;
69
70public class SocialStreamActivity extends ListActivity implements EdgeTriggerListener {
71 private static final String TAG = "SocialStreamActivity";
72
73 private static final String[] PROJ_ACTIVITIES = new String[] {
74 Activities._ID,
75 Activities.PACKAGE,
76 Activities.MIMETYPE,
77 Activities.AUTHOR_CONTACT_ID,
78 Contacts.AGGREGATE_ID,
79 Aggregates.DISPLAY_NAME,
80 Activities.PUBLISHED,
81 Activities.TITLE,
82 Activities.SUMMARY,
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -070083 Activities.THREAD_PUBLISHED,
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -070084 Activities.LINK,
85 Activities.THUMBNAIL,
Jeff Sharkey3f177592009-05-18 15:23:12 -070086 };
87
88 private static final int COL_ID = 0;
89 private static final int COL_PACKAGE = 1;
90 private static final int COL_MIMETYPE = 2;
91 private static final int COL_AUTHOR_CONTACT_ID = 3;
92 private static final int COL_AGGREGATE_ID = 4;
93 private static final int COL_DISPLAY_NAME = 5;
94 private static final int COL_PUBLISHED = 6;
95 private static final int COL_TITLE = 7;
96 private static final int COL_SUMMARY = 8;
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -070097 private static final int COL_THREAD_PUBLISHED = 9;
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -070098 private static final int COL_LINK = 10;
99 private static final int COL_THUMBNAIL = 11;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700100
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700101 public static final int PHOTO_SIZE = 54;
102 public static final int THUMBNAIL_SIZE = 54;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700103
104 private ListAdapter mAdapter;
105
106 private FloatyListView mListView;
107 private EdgeTriggerView mEdgeTrigger;
108 private FastTrackWindow mFastTrack;
109
110 private ContactsCache mContactsCache;
111 private MappingCache mMappingCache;
Dmitri Plotnikov06191cd2009-05-07 14:11:52 -0700112
113 @Override
114 protected void onCreate(Bundle icicle) {
115 super.onCreate(icicle);
116
Jeff Sharkey3f177592009-05-18 15:23:12 -0700117 setContentView(R.layout.social_list);
Dmitri Plotnikov06191cd2009-05-07 14:11:52 -0700118
Jeff Sharkey3f177592009-05-18 15:23:12 -0700119 mContactsCache = new ContactsCache(this);
120 mMappingCache = MappingCache.createAndFill(this);
121
122 Cursor cursor = managedQuery(Activities.CONTENT_URI, PROJ_ACTIVITIES, null, null);
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700123 mAdapter = new SocialAdapter(this, cursor, mContactsCache, mMappingCache);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700124
125 setListAdapter(mAdapter);
126
127 mListView = (FloatyListView)findViewById(android.R.id.list);
128
129 // Find and listen for edge triggers
130 mEdgeTrigger = (EdgeTriggerView)findViewById(R.id.edge_trigger);
131 mEdgeTrigger.setOnEdgeTriggerListener(this);
132 }
133
134 /** {@inheritDoc} */
135 public void onTrigger(float downX, float downY, int edge) {
136 // Find list item user triggered over
137 int position = mListView.pointToPosition((int)downX, (int)downY);
138
139 Cursor cursor = (Cursor)mAdapter.getItem(position);
140 long aggId = cursor.getLong(COL_AGGREGATE_ID);
141
142 Log.d(TAG, "onTrigger found position=" + position + ", contactId=" + aggId);
143
144 Uri aggUri = ContentUris.withAppendedId(ContactsContract.Aggregates.CONTENT_URI, aggId);
145
146 // Dismiss any existing window first
147 if (mFastTrack != null) {
148 mFastTrack.dismiss();
149 }
150
151 mFastTrack = new FastTrackWindow(this, mListView, aggUri, mMappingCache);
152 mListView.setFloatyWindow(mFastTrack, position);
153
154 }
155
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700156 @Override
157 protected void onListItemClick(ListView l, View v, int position, long id) {
158 Cursor cursor = (Cursor)getListAdapter().getItem(position);
159
160 // TODO check mime type and if it is supported, launch the corresponding app
161 String link = cursor.getString(COL_LINK);
162 if (link == null) {
163 return;
164 }
165
166 startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(link)));
167 }
168
Jeff Sharkey3f177592009-05-18 15:23:12 -0700169 /**
170 * List adapter for social stream data queried from
171 * {@link Activities#CONTENT_URI}.
172 */
173 private static class SocialAdapter extends CursorAdapter {
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700174 private final Context mContext;
175 private final LayoutInflater mInflater;
176 private final ContactsCache mContactsCache;
177 private final MappingCache mMappingCache;
Dmitri Plotnikov9a41d432009-05-19 18:33:46 -0700178 private final StyleSpan mTextStyleName;
Dmitri Plotnikov05f158f2009-05-21 11:37:15 -0700179 private final UnderlineSpan mTextStyleLink;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700180
181 private static class SocialHolder {
182 ImageView photo;
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700183 ImageView sourceIcon;
184 TextView content;
185 SpannableStringBuilder contentBuilder = new SpannableStringBuilder();
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700186 TextView summary;
Dmitri Plotnikov05f158f2009-05-21 11:37:15 -0700187 SpannableStringBuilder summaryBuilder = new SpannableStringBuilder();
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700188 ImageView thumbnail;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700189 TextView published;
190 }
191
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700192 public SocialAdapter(Context context, Cursor c, ContactsCache contactsCache,
193 MappingCache mappingCache) {
Jeff Sharkey3f177592009-05-18 15:23:12 -0700194 super(context, c, true);
195 mContext = context;
196 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
197 mContactsCache = contactsCache;
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700198 mMappingCache = mappingCache;
Dmitri Plotnikov9a41d432009-05-19 18:33:46 -0700199 mTextStyleName = new StyleSpan(android.graphics.Typeface.BOLD);
Dmitri Plotnikov05f158f2009-05-21 11:37:15 -0700200 mTextStyleLink = new UnderlineSpan();
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -0700201 }
202
203 @Override
204 public int getViewTypeCount() {
205 return 2;
206 }
207
208 @Override
209 public int getItemViewType(int position) {
210 Cursor cursor = (Cursor) getItem(position);
211 return isReply(cursor) ? 0 : 1;
212 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700213
214 @Override
215 public void bindView(View view, Context context, Cursor cursor) {
216 SocialHolder holder = (SocialHolder)view.getTag();
217
218 long contactId = cursor.getLong(COL_AUTHOR_CONTACT_ID);
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700219 String name = cursor.getString(COL_DISPLAY_NAME);
220 String title = cursor.getString(COL_TITLE);
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700221 String summary = cursor.getString(COL_SUMMARY);
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700222 long published = cursor.getLong(COL_PUBLISHED);
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700223 byte[] thumbnailBlob = cursor.getBlob(COL_THUMBNAIL);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700224
225 // TODO: trigger async query to find actual name and photo instead
226 // of using this lazy caching mechanism
Dmitri Plotnikov9a41d432009-05-19 18:33:46 -0700227 Bitmap photo = mContactsCache.getPhoto(contactId);
228 if (photo != null) {
229 holder.photo.setImageBitmap(photo);
230 } else {
231 holder.photo.setImageResource(R.drawable.ic_contact_list_picture);
232 }
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700233 holder.contentBuilder.clear();
234 holder.contentBuilder.append(name);
Dmitri Plotnikov9a41d432009-05-19 18:33:46 -0700235 holder.contentBuilder.append(" ");
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700236 holder.contentBuilder.append(title);
Dmitri Plotnikov9a41d432009-05-19 18:33:46 -0700237 holder.contentBuilder.setSpan(mTextStyleName, 0, name.length(), 0);
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700238 holder.content.setText(holder.contentBuilder);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700239
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700240 if (summary == null) {
241 holder.summary.setVisibility(View.GONE);
242 } else {
Dmitri Plotnikov05f158f2009-05-21 11:37:15 -0700243 holder.summaryBuilder.clear();
244 holder.summaryBuilder.append(summary);
245 holder.summaryBuilder.setSpan(mTextStyleLink, 0, summary.length(), 0);
246 holder.summary.setText(holder.summaryBuilder);
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700247 holder.summary.setVisibility(View.VISIBLE);
248 }
249
250 if (thumbnailBlob != null) {
251 Bitmap thumbnail =
252 BitmapFactory.decodeByteArray(thumbnailBlob, 0, thumbnailBlob.length);
253 holder.thumbnail.setImageBitmap(thumbnail);
254 holder.thumbnail.setVisibility(View.VISIBLE);
255 } else {
256 holder.thumbnail.setVisibility(View.GONE);
257 }
258
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700259 CharSequence relativePublished = DateUtils.getRelativeTimeSpanString(published,
260 System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700261 holder.published.setText(relativePublished);
262
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -0700263 if (holder.sourceIcon != null) {
264 String packageName = cursor.getString(COL_PACKAGE);
265 String mimeType = cursor.getString(COL_MIMETYPE);
266 Mapping mapping = mMappingCache.getMapping(packageName, mimeType);
267 if (mapping != null && mapping.icon != null) {
268 holder.sourceIcon.setImageBitmap(mapping.icon);
269 } else {
270 holder.sourceIcon.setImageDrawable(null);
271 }
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700272 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700273 }
274
275 @Override
276 public View newView(Context context, Cursor cursor, ViewGroup parent) {
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -0700277 View view = mInflater.inflate(
278 isReply(cursor) ? R.layout.social_list_item_reply : R.layout.social_list_item,
279 parent, false);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700280
281 SocialHolder holder = new SocialHolder();
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700282 holder.photo = (ImageView) view.findViewById(R.id.photo);
283 holder.sourceIcon = (ImageView) view.findViewById(R.id.sourceIcon);
284 holder.content = (TextView) view.findViewById(R.id.content);
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700285 holder.summary = (TextView) view.findViewById(R.id.summary);
286 holder.thumbnail = (ImageView) view.findViewById(R.id.thumbnail);
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700287 holder.published = (TextView) view.findViewById(R.id.published);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700288 view.setTag(holder);
289
290 return view;
291 }
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -0700292
293 private boolean isReply(Cursor cursor) {
294
295 /*
296 * Comparing the message timestamp to the thread timestamp rather than checking the
297 * in_reply_to field. The rationale for this approach is that in the case when the
298 * original message to which the reply was posted is missing, we want to display
299 * the message as if it was an original; otherwise it would appear to be a reply
300 * to whatever message preceded it in the list. In the case when the original message
301 * of the thread is missing, the two timestamps will be the same.
302 */
303 long published = cursor.getLong(COL_PUBLISHED);
304 long threadPublished = cursor.getLong(COL_THREAD_PUBLISHED);
305 return published != threadPublished;
306 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700307 }
308
309 /**
310 * Keep a cache that maps from {@link Contacts#_ID} to {@link Photo#PHOTO}
311 * values.
312 */
313 private static class ContactsCache {
314 private static final String TAG = "ContactsCache";
315
316 private static final String[] PROJ_DETAILS = new String[] {
317 Data.MIMETYPE,
318 Data.CONTACT_ID,
319 Photo.PHOTO,
320 };
321
322 private static final int COL_MIMETYPE = 0;
323 private static final int COL_CONTACT_ID = 1;
Jeff Sharkey8da253a2009-05-18 21:23:19 -0700324 private static final int COL_PHOTO = 2;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700325
326 private HashMap<Long, Bitmap> mPhoto = new HashMap<Long, Bitmap>();
327
328 public ContactsCache(Context context) {
329 Log.d(TAG, "building ContactsCache...");
330
331 ContentResolver resolver = context.getContentResolver();
332 Cursor cursor = resolver.query(Data.CONTENT_URI, PROJ_DETAILS,
333 Data.MIMETYPE + "=?", new String[] { Photo.CONTENT_ITEM_TYPE }, null);
334
335 while (cursor.moveToNext()) {
336 long contactId = cursor.getLong(COL_CONTACT_ID);
337 String mimeType = cursor.getString(COL_MIMETYPE);
338 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
339 byte[] photoBlob = cursor.getBlob(COL_PHOTO);
340 Bitmap photo = BitmapFactory.decodeByteArray(photoBlob, 0, photoBlob.length);
341 photo = Utilities.createBitmapThumbnail(photo, context, PHOTO_SIZE);
342
343 mPhoto.put(contactId, photo);
344 }
345 }
346
347 cursor.close();
348 Log.d(TAG, "done building ContactsCache");
349 }
350
351 public Bitmap getPhoto(long contactId) {
352 return mPhoto.get(contactId);
353 }
354 }
355
356 /**
357 * Store a parsed <code>RemoteViewsMapping</code> object, which maps
358 * mime-types to <code>RemoteViews</code> XML resources and possible icons.
359 */
360 public static class MappingCache {
361 private static final String TAG = "MappingCache";
362
363 private static final String TAG_MAPPINGSET = "MappingSet";
364 private static final String TAG_MAPPING = "Mapping";
365
366 private static final String MAPPING_METADATA = "com.android.contacts.stylemap";
367
368 private LinkedList<Mapping> mappings = new LinkedList<Mapping>();
369
370 private MappingCache() {
371 }
372
373 public static class Mapping {
374 String packageName;
375 String mimeType;
376 int remoteViewsRes;
377 Bitmap icon;
378 }
379
380 public void addMapping(Mapping mapping) {
381 mappings.add(mapping);
382 }
383
384 /**
385 * Find matching <code>RemoteViews</code> XML resource for requested
386 * package and mime-type. Returns -1 if no mapping found.
387 */
388 public Mapping getMapping(String packageName, String mimeType) {
389 for (Mapping mapping : mappings) {
390 if (mapping.packageName.equals(packageName) && mapping.mimeType.equals(mimeType)) {
391 return mapping;
392 }
393 }
394 return null;
395 }
396
397 /**
398 * Create a new {@link MappingCache} object and fill by walking across
399 * all packages to find those that provide mappings.
400 */
401 public static MappingCache createAndFill(Context context) {
402 Log.d(TAG, "searching for mimetype mappings...");
403 final PackageManager pm = context.getPackageManager();
404 MappingCache building = new MappingCache();
405 List<ApplicationInfo> installed = pm
406 .getInstalledApplications(PackageManager.GET_META_DATA);
407 for (ApplicationInfo info : installed) {
408 if (info.metaData != null && info.metaData.containsKey(MAPPING_METADATA)) {
409 try {
410 // Found metadata, so clone into their context to
411 // inflate reference
412 Context theirContext = context.createPackageContext(info.packageName, 0);
413 XmlPullParser mappingParser = info.loadXmlMetaData(pm, MAPPING_METADATA);
414 building.inflateMappings(theirContext, info.uid, info.packageName,
415 mappingParser);
416 } catch (NameNotFoundException e) {
417 Log.w(TAG, "Problem creating context for remote package", e);
418 } catch (InflateException e) {
419 Log.w(TAG, "Problem inflating MappingSet from remote package", e);
420 }
421 }
422 }
423 return building;
424 }
425
426 public static class InflateException extends Exception {
427 public InflateException(String message) {
428 super(message);
429 }
430
431 public InflateException(String message, Throwable throwable) {
432 super(message, throwable);
433 }
434 }
435
436 /**
437 * Inflate a <code>MappingSet</code> from an XML resource, assuming the
438 * given package name as the source.
439 */
440 public void inflateMappings(Context context, int uid, String packageName,
441 XmlPullParser parser) throws InflateException {
442 final AttributeSet attrs = Xml.asAttributeSet(parser);
443
444 try {
445 int type;
446 while ((type = parser.next()) != XmlPullParser.START_TAG
447 && type != XmlPullParser.END_DOCUMENT) {
448 // Drain comments and whitespace
449 }
450
451 if (type != XmlPullParser.START_TAG) {
452 throw new InflateException("No start tag found");
453 }
454
455 if (!TAG_MAPPINGSET.equals(parser.getName())) {
456 throw new InflateException("Top level element must be MappingSet");
457 }
458
459 // Parse all children actions
460 final int depth = parser.getDepth();
461 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
462 && type != XmlPullParser.END_DOCUMENT) {
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700463 if (type == XmlPullParser.END_TAG) {
Jeff Sharkey3f177592009-05-18 15:23:12 -0700464 continue;
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700465 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700466
467 if (!TAG_MAPPING.equals(parser.getName())) {
468 throw new InflateException("Expected Mapping tag");
469 }
470
471 // Parse kind, mime-type, and RemoteViews reference
472 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Mapping);
473
474 Mapping mapping = new Mapping();
475 mapping.packageName = packageName;
476 mapping.mimeType = a.getString(R.styleable.Mapping_mimeType);
477 mapping.remoteViewsRes = a.getResourceId(R.styleable.Mapping_remoteViews, -1);
478
479 // Read and resize icon if provided
480 int iconRes = a.getResourceId(R.styleable.Mapping_icon, -1);
481 if (iconRes != -1) {
482 mapping.icon = BitmapFactory
483 .decodeResource(context.getResources(), iconRes);
484 mapping.icon = Utilities.createBitmapThumbnail(mapping.icon, context,
485 FastTrackWindow.ICON_SIZE);
486 }
487
488 addMapping(mapping);
489 Log.d(TAG, "Added mapping for packageName=" + mapping.packageName
490 + ", mimetype=" + mapping.mimeType);
491 }
492 } catch (XmlPullParserException e) {
493 throw new InflateException("Problem reading XML", e);
494 } catch (IOException e) {
495 throw new InflateException("Problem reading XML", e);
496 }
497 }
498 }
499
500 /**
501 * Borrowed from Launcher for {@link Bitmap} resizing.
502 */
503 static final class Utilities {
504 private static final Paint sPaint = new Paint();
505 private static final Rect sBounds = new Rect();
506 private static final Rect sOldBounds = new Rect();
507 private static Canvas sCanvas = new Canvas();
508
509 static {
510 sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
511 Paint.FILTER_BITMAP_FLAG));
512 }
513
514 /**
515 * Returns a Bitmap representing the thumbnail of the specified Bitmap.
516 * The size of the thumbnail is defined by the dimension
517 * android.R.dimen.launcher_application_icon_size. This method is not
518 * thread-safe and should be invoked on the UI thread only.
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700519 *
Jeff Sharkey3f177592009-05-18 15:23:12 -0700520 * @param bitmap The bitmap to get a thumbnail of.
521 * @param context The application's context.
522 * @return A thumbnail for the specified bitmap or the bitmap itself if
523 * the thumbnail could not be created.
524 */
525 static Bitmap createBitmapThumbnail(Bitmap bitmap, Context context, int size) {
526 int width = size;
527 int height = size;
528
529 final int bitmapWidth = bitmap.getWidth();
530 final int bitmapHeight = bitmap.getHeight();
531
532 if (width > 0 && height > 0 && (width < bitmapWidth || height < bitmapHeight)) {
533 final float ratio = (float)bitmapWidth / bitmapHeight;
534
535 if (bitmapWidth > bitmapHeight) {
536 height = (int)(width / ratio);
537 } else if (bitmapHeight > bitmapWidth) {
538 width = (int)(height * ratio);
539 }
540
541 final Bitmap.Config c = (width == size && height == size) ? bitmap.getConfig()
542 : Bitmap.Config.ARGB_8888;
543 final Bitmap thumb = Bitmap.createBitmap(size, size, c);
544 final Canvas canvas = sCanvas;
545 final Paint paint = sPaint;
546 canvas.setBitmap(thumb);
547 paint.setDither(false);
548 paint.setFilterBitmap(true);
549 sBounds.set((size - width) / 2, (size - height) / 2, width, height);
550 sOldBounds.set(0, 0, bitmapWidth, bitmapHeight);
551 canvas.drawBitmap(bitmap, sOldBounds, sBounds, paint);
552 return thumb;
553 }
554
555 return bitmap;
556 }
Dmitri Plotnikov06191cd2009-05-07 14:11:52 -0700557 }
558}