blob: 11f9367cdf37ccc45f3a25ee179a0158b4ee7456 [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 Sharkey802b2052009-08-04 14:21:06 -070019import com.android.contacts.ui.FastTrackWindow;
Dmitri Plotnikov06191cd2009-05-07 14:11:52 -070020
Jeff Sharkey3f177592009-05-18 15:23:12 -070021import org.xmlpull.v1.XmlPullParser;
22import org.xmlpull.v1.XmlPullParserException;
23
24import android.app.ListActivity;
25import android.content.ContentResolver;
26import android.content.ContentUris;
27import android.content.Context;
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -070028import android.content.Intent;
Jeff Sharkey3f177592009-05-18 15:23:12 -070029import android.content.pm.ApplicationInfo;
30import android.content.pm.PackageManager;
31import android.content.pm.PackageManager.NameNotFoundException;
Jeff Sharkey549aa162009-05-21 01:33:30 -070032import android.content.res.Resources;
Jeff Sharkey3f177592009-05-18 15:23:12 -070033import android.content.res.TypedArray;
34import android.database.Cursor;
35import android.graphics.Bitmap;
36import android.graphics.BitmapFactory;
37import android.graphics.Canvas;
38import android.graphics.Paint;
39import android.graphics.PaintFlagsDrawFilter;
Jeff Sharkey3f177592009-05-18 15:23:12 -070040import android.graphics.Rect;
Jeff Sharkey3f177592009-05-18 15:23:12 -070041import android.net.Uri;
42import android.os.Bundle;
Evan Millar66388be2009-05-28 15:41:07 -070043import android.provider.ContactsContract;
Dmitri Plotnikove1cd6792009-07-27 20:28:17 -070044import android.provider.ContactsContract.Contacts;
Evan Millar66388be2009-05-28 15:41:07 -070045import android.provider.ContactsContract.CommonDataKinds;
Evan Millar66388be2009-05-28 15:41:07 -070046import android.provider.ContactsContract.Data;
Jeff Sharkey802b2052009-08-04 14:21:06 -070047import android.provider.ContactsContract.Intents;
Dmitri Plotnikov39466592009-07-27 11:23:51 -070048import android.provider.ContactsContract.RawContacts;
Evan Millar66388be2009-05-28 15:41:07 -070049import android.provider.ContactsContract.CommonDataKinds.Photo;
50import android.provider.SocialContract.Activities;
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -070051import android.text.SpannableStringBuilder;
Jeff Sharkey3f177592009-05-18 15:23:12 -070052import android.text.format.DateUtils;
Dmitri Plotnikov9a41d432009-05-19 18:33:46 -070053import android.text.style.StyleSpan;
Dmitri Plotnikov05f158f2009-05-21 11:37:15 -070054import android.text.style.UnderlineSpan;
Jeff Sharkey3f177592009-05-18 15:23:12 -070055import android.util.AttributeSet;
56import android.util.Log;
57import android.util.Xml;
Jeff Sharkey549aa162009-05-21 01:33:30 -070058import android.view.KeyEvent;
Jeff Sharkey3f177592009-05-18 15:23:12 -070059import android.view.LayoutInflater;
60import android.view.View;
61import android.view.ViewGroup;
Jeff Sharkey549aa162009-05-21 01:33:30 -070062import android.view.View.OnClickListener;
Jeff Sharkey3f177592009-05-18 15:23:12 -070063import android.widget.CursorAdapter;
64import android.widget.ImageView;
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -070065import android.widget.ListView;
Dmitri Plotnikov39466592009-07-27 11:23:51 -070066import android.widget.RemoteViews;
Jeff Sharkey3f177592009-05-18 15:23:12 -070067import android.widget.TextView;
68
69import java.io.IOException;
70import java.util.HashMap;
Jeff Sharkey3f177592009-05-18 15:23:12 -070071import java.util.List;
72
Jeff Sharkey170f7752009-08-16 00:12:48 -070073public class SocialStreamActivity extends ListActivity implements OnClickListener {
Jeff Sharkey3f177592009-05-18 15:23:12 -070074 private static final String TAG = "SocialStreamActivity";
75
76 private static final String[] PROJ_ACTIVITIES = new String[] {
77 Activities._ID,
Jeff Sharkeyc6ad3ab2009-07-21 19:30:15 -070078 Activities.RES_PACKAGE,
Jeff Sharkey3f177592009-05-18 15:23:12 -070079 Activities.MIMETYPE,
80 Activities.AUTHOR_CONTACT_ID,
Dmitri Plotnikove1cd6792009-07-27 20:28:17 -070081 RawContacts.CONTACT_ID,
82 Contacts.DISPLAY_NAME,
Jeff Sharkey3f177592009-05-18 15:23:12 -070083 Activities.PUBLISHED,
84 Activities.TITLE,
85 Activities.SUMMARY,
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -070086 Activities.THREAD_PUBLISHED,
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -070087 Activities.LINK,
88 Activities.THUMBNAIL,
Jeff Sharkey3f177592009-05-18 15:23:12 -070089 };
90
91 private static final int COL_ID = 0;
92 private static final int COL_PACKAGE = 1;
93 private static final int COL_MIMETYPE = 2;
94 private static final int COL_AUTHOR_CONTACT_ID = 3;
95 private static final int COL_AGGREGATE_ID = 4;
96 private static final int COL_DISPLAY_NAME = 5;
97 private static final int COL_PUBLISHED = 6;
98 private static final int COL_TITLE = 7;
99 private static final int COL_SUMMARY = 8;
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -0700100 private static final int COL_THREAD_PUBLISHED = 9;
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700101 private static final int COL_LINK = 10;
102 private static final int COL_THUMBNAIL = 11;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700103
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700104 public static final int PHOTO_SIZE = 54;
105 public static final int THUMBNAIL_SIZE = 54;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700106
Jeff Sharkey549aa162009-05-21 01:33:30 -0700107 private SocialAdapter mAdapter;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700108
Jeff Sharkey549aa162009-05-21 01:33:30 -0700109 private ListView mListView;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700110 private FastTrackWindow mFastTrack;
Jeff Sharkey549aa162009-05-21 01:33:30 -0700111 private MappingCache mMappingCache;
112
Jeff Sharkey3f177592009-05-18 15:23:12 -0700113 private ContactsCache mContactsCache;
Dmitri Plotnikov06191cd2009-05-07 14:11:52 -0700114
115 @Override
116 protected void onCreate(Bundle icicle) {
117 super.onCreate(icicle);
118
Jeff Sharkey3f177592009-05-18 15:23:12 -0700119 setContentView(R.layout.social_list);
Dmitri Plotnikov06191cd2009-05-07 14:11:52 -0700120
Jeff Sharkey3f177592009-05-18 15:23:12 -0700121 mContactsCache = new ContactsCache(this);
122 mMappingCache = MappingCache.createAndFill(this);
123
124 Cursor cursor = managedQuery(Activities.CONTENT_URI, PROJ_ACTIVITIES, null, null);
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700125 mAdapter = new SocialAdapter(this, cursor, mContactsCache, mMappingCache);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700126 mAdapter.setPhotoListener(this);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700127
128 setListAdapter(mAdapter);
129
Jeff Sharkey549aa162009-05-21 01:33:30 -0700130 mListView = getListView();
131 mFastTrack = new FastTrackWindow(this);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700132 }
133
134 /** {@inheritDoc} */
135 public void onClick(View v) {
136 // Clicked on photo, so show fast-track
Jeff Sharkey39261272009-06-03 19:15:09 -0700137 showFastTrack(v, (Long)v.getTag());
Jeff Sharkey3f177592009-05-18 15:23:12 -0700138 }
139
Jeff Sharkey549aa162009-05-21 01:33:30 -0700140 private int[] mLocation = new int[2];
Jeff Sharkey39261272009-06-03 19:15:09 -0700141 private Rect mRect = new Rect();
Jeff Sharkey549aa162009-05-21 01:33:30 -0700142
143 private void showFastTrack(View anchor, long aggId) {
Dmitri Plotnikove1cd6792009-07-27 20:28:17 -0700144 Uri aggUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, aggId);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700145
Jeff Sharkey549aa162009-05-21 01:33:30 -0700146 anchor.getLocationInWindow(mLocation);
Jeff Sharkey39261272009-06-03 19:15:09 -0700147 mRect.left = mLocation[0];
148 mRect.top = mLocation[1];
149 mRect.right = mRect.left + anchor.getWidth();
150 mRect.bottom = mRect.top + anchor.getHeight();
Jeff Sharkey549aa162009-05-21 01:33:30 -0700151
152 mFastTrack.dismiss();
Jeff Sharkey802b2052009-08-04 14:21:06 -0700153 mFastTrack.show(aggUri, mRect, Intents.MODE_MEDIUM);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700154 }
155
156 /** {@inheritDoc} */
157 @Override
158 public boolean onKeyDown(int keyCode, KeyEvent event) {
159 // Back key dismisses fast-track when its visible
160 if (keyCode == KeyEvent.KEYCODE_BACK && mFastTrack.isShowing()) {
Jeff Sharkey3f177592009-05-18 15:23:12 -0700161 mFastTrack.dismiss();
Jeff Sharkey549aa162009-05-21 01:33:30 -0700162 return true;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700163 }
164
Jeff Sharkey549aa162009-05-21 01:33:30 -0700165 return super.onKeyDown(keyCode, event);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700166 }
167
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700168 @Override
169 protected void onListItemClick(ListView l, View v, int position, long id) {
170 Cursor cursor = (Cursor)getListAdapter().getItem(position);
171
172 // TODO check mime type and if it is supported, launch the corresponding app
173 String link = cursor.getString(COL_LINK);
174 if (link == null) {
175 return;
176 }
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700177 startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(link)));
178 }
179
Jeff Sharkey3f177592009-05-18 15:23:12 -0700180 /**
181 * List adapter for social stream data queried from
182 * {@link Activities#CONTENT_URI}.
183 */
184 private static class SocialAdapter extends CursorAdapter {
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700185 private final Context mContext;
186 private final LayoutInflater mInflater;
187 private final ContactsCache mContactsCache;
188 private final MappingCache mMappingCache;
Dmitri Plotnikov9a41d432009-05-19 18:33:46 -0700189 private final StyleSpan mTextStyleName;
Dmitri Plotnikov05f158f2009-05-21 11:37:15 -0700190 private final UnderlineSpan mTextStyleLink;
Jeff Sharkey549aa162009-05-21 01:33:30 -0700191 private OnClickListener mPhotoListener;
192 private SpannableStringBuilder mBuilder = new SpannableStringBuilder();
Jeff Sharkey3f177592009-05-18 15:23:12 -0700193
Jeff Sharkey39261272009-06-03 19:15:09 -0700194 public static final int TYPE_ACTIVITY = 0;
195 public static final int TYPE_REPLY = 1;
196
Jeff Sharkey3f177592009-05-18 15:23:12 -0700197 private static class SocialHolder {
198 ImageView photo;
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700199 ImageView sourceIcon;
200 TextView content;
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700201 TextView summary;
202 ImageView thumbnail;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700203 TextView published;
204 }
205
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700206 public SocialAdapter(Context context, Cursor c, ContactsCache contactsCache,
207 MappingCache mappingCache) {
Jeff Sharkey3f177592009-05-18 15:23:12 -0700208 super(context, c, true);
209 mContext = context;
210 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
211 mContactsCache = contactsCache;
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700212 mMappingCache = mappingCache;
Dmitri Plotnikov9a41d432009-05-19 18:33:46 -0700213 mTextStyleName = new StyleSpan(android.graphics.Typeface.BOLD);
Dmitri Plotnikov05f158f2009-05-21 11:37:15 -0700214 mTextStyleLink = new UnderlineSpan();
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -0700215 }
216
Jeff Sharkey549aa162009-05-21 01:33:30 -0700217 public void setPhotoListener(OnClickListener listener) {
218 mPhotoListener = listener;
219 }
220
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -0700221 @Override
222 public int getViewTypeCount() {
223 return 2;
224 }
225
226 @Override
227 public int getItemViewType(int position) {
228 Cursor cursor = (Cursor) getItem(position);
Jeff Sharkey39261272009-06-03 19:15:09 -0700229 return isReply(cursor) ? TYPE_ACTIVITY : TYPE_REPLY;
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -0700230 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700231
232 @Override
233 public void bindView(View view, Context context, Cursor cursor) {
234 SocialHolder holder = (SocialHolder)view.getTag();
235
Jeff Sharkey549aa162009-05-21 01:33:30 -0700236 long aggId = cursor.getLong(COL_AGGREGATE_ID);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700237 long contactId = cursor.getLong(COL_AUTHOR_CONTACT_ID);
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700238 String name = cursor.getString(COL_DISPLAY_NAME);
239 String title = cursor.getString(COL_TITLE);
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700240 String summary = cursor.getString(COL_SUMMARY);
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700241 long published = cursor.getLong(COL_PUBLISHED);
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700242 byte[] thumbnailBlob = cursor.getBlob(COL_THUMBNAIL);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700243
244 // TODO: trigger async query to find actual name and photo instead
245 // of using this lazy caching mechanism
Dmitri Plotnikov9a41d432009-05-19 18:33:46 -0700246 Bitmap photo = mContactsCache.getPhoto(contactId);
247 if (photo != null) {
248 holder.photo.setImageBitmap(photo);
249 } else {
250 holder.photo.setImageResource(R.drawable.ic_contact_list_picture);
251 }
Jeff Sharkey549aa162009-05-21 01:33:30 -0700252 holder.photo.setTag(aggId);
253
254 mBuilder.clear();
255 mBuilder.append(name);
256 mBuilder.append(" ");
257 mBuilder.append(title);
258 mBuilder.setSpan(mTextStyleName, 0, name.length(), 0);
259 holder.content.setText(mBuilder);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700260
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700261 if (summary == null) {
262 holder.summary.setVisibility(View.GONE);
263 } else {
Jeff Sharkey39261272009-06-03 19:15:09 -0700264 mBuilder.clear();
265 mBuilder.append(summary);
266 mBuilder.setSpan(mTextStyleLink, 0, summary.length(), 0);
267 holder.summary.setText(mBuilder);
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700268 holder.summary.setVisibility(View.VISIBLE);
269 }
270
271 if (thumbnailBlob != null) {
272 Bitmap thumbnail =
273 BitmapFactory.decodeByteArray(thumbnailBlob, 0, thumbnailBlob.length);
274 holder.thumbnail.setImageBitmap(thumbnail);
275 holder.thumbnail.setVisibility(View.VISIBLE);
276 } else {
277 holder.thumbnail.setVisibility(View.GONE);
278 }
279
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700280 CharSequence relativePublished = DateUtils.getRelativeTimeSpanString(published,
281 System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700282 holder.published.setText(relativePublished);
283
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -0700284 if (holder.sourceIcon != null) {
285 String packageName = cursor.getString(COL_PACKAGE);
286 String mimeType = cursor.getString(COL_MIMETYPE);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700287 Mapping mapping = mMappingCache.findMapping(packageName, mimeType);
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -0700288 if (mapping != null && mapping.icon != null) {
289 holder.sourceIcon.setImageBitmap(mapping.icon);
290 } else {
291 holder.sourceIcon.setImageDrawable(null);
292 }
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700293 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700294 }
295
296 @Override
297 public View newView(Context context, Cursor cursor, ViewGroup parent) {
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -0700298 View view = mInflater.inflate(
299 isReply(cursor) ? R.layout.social_list_item_reply : R.layout.social_list_item,
300 parent, false);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700301
302 SocialHolder holder = new SocialHolder();
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700303 holder.photo = (ImageView) view.findViewById(R.id.photo);
304 holder.sourceIcon = (ImageView) view.findViewById(R.id.sourceIcon);
305 holder.content = (TextView) view.findViewById(R.id.content);
Dmitri Plotnikov672cbe62009-05-20 19:07:59 -0700306 holder.summary = (TextView) view.findViewById(R.id.summary);
307 holder.thumbnail = (ImageView) view.findViewById(R.id.thumbnail);
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700308 holder.published = (TextView) view.findViewById(R.id.published);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700309 view.setTag(holder);
310
Jeff Sharkey170f7752009-08-16 00:12:48 -0700311 holder.photo.setOnClickListener(mPhotoListener);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700312
Jeff Sharkey3f177592009-05-18 15:23:12 -0700313 return view;
314 }
Dmitri Plotnikovf0eb9f52009-05-20 14:44:56 -0700315
316 private boolean isReply(Cursor cursor) {
317
318 /*
319 * Comparing the message timestamp to the thread timestamp rather than checking the
320 * in_reply_to field. The rationale for this approach is that in the case when the
321 * original message to which the reply was posted is missing, we want to display
322 * the message as if it was an original; otherwise it would appear to be a reply
323 * to whatever message preceded it in the list. In the case when the original message
324 * of the thread is missing, the two timestamps will be the same.
325 */
326 long published = cursor.getLong(COL_PUBLISHED);
327 long threadPublished = cursor.getLong(COL_THREAD_PUBLISHED);
328 return published != threadPublished;
329 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700330 }
331
332 /**
Dmitri Plotnikov39466592009-07-27 11:23:51 -0700333 * Keep a cache that maps from {@link RawContacts#_ID} to {@link Photo#PHOTO}
Jeff Sharkey3f177592009-05-18 15:23:12 -0700334 * values.
335 */
336 private static class ContactsCache {
337 private static final String TAG = "ContactsCache";
338
339 private static final String[] PROJ_DETAILS = new String[] {
340 Data.MIMETYPE,
Dmitri Plotnikove1cd6792009-07-27 20:28:17 -0700341 Data.RAW_CONTACT_ID,
Jeff Sharkey3f177592009-05-18 15:23:12 -0700342 Photo.PHOTO,
343 };
344
345 private static final int COL_MIMETYPE = 0;
Dmitri Plotnikove1cd6792009-07-27 20:28:17 -0700346 private static final int COL_RAW_CONTACT_ID = 1;
Jeff Sharkey8da253a2009-05-18 21:23:19 -0700347 private static final int COL_PHOTO = 2;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700348
349 private HashMap<Long, Bitmap> mPhoto = new HashMap<Long, Bitmap>();
350
351 public ContactsCache(Context context) {
352 Log.d(TAG, "building ContactsCache...");
353
354 ContentResolver resolver = context.getContentResolver();
355 Cursor cursor = resolver.query(Data.CONTENT_URI, PROJ_DETAILS,
356 Data.MIMETYPE + "=?", new String[] { Photo.CONTENT_ITEM_TYPE }, null);
357
358 while (cursor.moveToNext()) {
Dmitri Plotnikove1cd6792009-07-27 20:28:17 -0700359 long contactId = cursor.getLong(COL_RAW_CONTACT_ID);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700360 String mimeType = cursor.getString(COL_MIMETYPE);
361 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
362 byte[] photoBlob = cursor.getBlob(COL_PHOTO);
363 Bitmap photo = BitmapFactory.decodeByteArray(photoBlob, 0, photoBlob.length);
364 photo = Utilities.createBitmapThumbnail(photo, context, PHOTO_SIZE);
365
366 mPhoto.put(contactId, photo);
367 }
368 }
369
370 cursor.close();
371 Log.d(TAG, "done building ContactsCache");
372 }
373
374 public Bitmap getPhoto(long contactId) {
375 return mPhoto.get(contactId);
376 }
377 }
378
379 /**
Jeff Sharkey549aa162009-05-21 01:33:30 -0700380 * Store a mapping from a package name and mime-type pair to a set of
381 * {@link RemoteViews}, default icon, and column to use from the
382 * {@link Data} table to use as a summary.
Jeff Sharkey170f7752009-08-16 00:12:48 -0700383 *
384 * @deprecated use {@link ContactsSource} instead
Jeff Sharkey3f177592009-05-18 15:23:12 -0700385 */
Jeff Sharkey170f7752009-08-16 00:12:48 -0700386 @Deprecated
Jeff Sharkey549aa162009-05-21 01:33:30 -0700387 public static class Mapping {
388 String packageName;
389 String mimeType;
390 String summaryColumn;
Jeff Sharkey39261272009-06-03 19:15:09 -0700391 String detailColumn;
Jeff Sharkey549aa162009-05-21 01:33:30 -0700392 int remoteViewsRes;
393 Bitmap icon;
394
395 public Mapping() {
396 }
397
398 public Mapping(String packageName, String mimeType) {
399 this.packageName = packageName;
400 this.mimeType = mimeType;
401 }
402 }
403
404 /**
405 * Store a parsed <code>Mapping</code> object, which maps package and
406 * mime-type combinations to {@link RemoteViews} XML resources, default
407 * icons, and summary columns in the {@link Data} table.
Jeff Sharkey170f7752009-08-16 00:12:48 -0700408 *
409 * @deprecated use {@link Sources} instead
Jeff Sharkey549aa162009-05-21 01:33:30 -0700410 */
Jeff Sharkey170f7752009-08-16 00:12:48 -0700411 @Deprecated
Jeff Sharkey549aa162009-05-21 01:33:30 -0700412 public static class MappingCache extends HashMap<String, Mapping> {
Jeff Sharkey3f177592009-05-18 15:23:12 -0700413 private static final String TAG = "MappingCache";
414
415 private static final String TAG_MAPPINGSET = "MappingSet";
416 private static final String TAG_MAPPING = "Mapping";
417
418 private static final String MAPPING_METADATA = "com.android.contacts.stylemap";
419
Jeff Sharkey3f177592009-05-18 15:23:12 -0700420
Jeff Sharkey549aa162009-05-21 01:33:30 -0700421 /**
422 * Only allow inflating through
423 * {@link MappingCache#createAndFill(Context)}.
424 */
Jeff Sharkey3f177592009-05-18 15:23:12 -0700425 private MappingCache() {
426 }
427
Jeff Sharkey549aa162009-05-21 01:33:30 -0700428 /**
429 * Add a {@link Mapping} instance to this cache, correctly using
430 * {@link #generateKey(String, String)} when storing.
431 */
Jeff Sharkey3f177592009-05-18 15:23:12 -0700432 public void addMapping(Mapping mapping) {
Jeff Sharkey549aa162009-05-21 01:33:30 -0700433 String hashKey = generateKey(mapping.packageName, mapping.mimeType);
434 put(hashKey, mapping);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700435 }
436
437 /**
Jeff Sharkey549aa162009-05-21 01:33:30 -0700438 * Generate a key used internally for mapping a specific package name
439 * and mime-type to a {@link Mapping}.
Jeff Sharkey3f177592009-05-18 15:23:12 -0700440 */
Jeff Sharkey549aa162009-05-21 01:33:30 -0700441 private String generateKey(String packageName, String mimeType) {
442 return packageName + ";" + mimeType;
443 }
444
445 /**
446 * Find matching mapping for requested package and mime-type. Returns
447 * null if no mapping found.
448 */
449 public Mapping findMapping(String packageName, String mimeType) {
450 // Search for common mapping first
451 final String commonMapping = generateKey(CommonDataKinds.PACKAGE_COMMON, mimeType);
452 if (containsKey(commonMapping)) {
453 return get(commonMapping);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700454 }
Jeff Sharkey549aa162009-05-21 01:33:30 -0700455
456 // Otherwise search for package-specific mapping
457 final String specificMapping = generateKey(packageName, mimeType);
458 return get(specificMapping);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700459 }
460
461 /**
462 * Create a new {@link MappingCache} object and fill by walking across
463 * all packages to find those that provide mappings.
464 */
465 public static MappingCache createAndFill(Context context) {
Jeff Sharkey549aa162009-05-21 01:33:30 -0700466 Log.d(TAG, "building mime-type mapping cache...");
Jeff Sharkey3f177592009-05-18 15:23:12 -0700467 final PackageManager pm = context.getPackageManager();
468 MappingCache building = new MappingCache();
469 List<ApplicationInfo> installed = pm
470 .getInstalledApplications(PackageManager.GET_META_DATA);
471 for (ApplicationInfo info : installed) {
472 if (info.metaData != null && info.metaData.containsKey(MAPPING_METADATA)) {
473 try {
474 // Found metadata, so clone into their context to
475 // inflate reference
476 Context theirContext = context.createPackageContext(info.packageName, 0);
477 XmlPullParser mappingParser = info.loadXmlMetaData(pm, MAPPING_METADATA);
478 building.inflateMappings(theirContext, info.uid, info.packageName,
479 mappingParser);
480 } catch (NameNotFoundException e) {
481 Log.w(TAG, "Problem creating context for remote package", e);
482 } catch (InflateException e) {
483 Log.w(TAG, "Problem inflating MappingSet from remote package", e);
484 }
485 }
486 }
487 return building;
488 }
489
490 public static class InflateException extends Exception {
491 public InflateException(String message) {
492 super(message);
493 }
494
495 public InflateException(String message, Throwable throwable) {
496 super(message, throwable);
497 }
498 }
499
500 /**
501 * Inflate a <code>MappingSet</code> from an XML resource, assuming the
502 * given package name as the source.
503 */
504 public void inflateMappings(Context context, int uid, String packageName,
505 XmlPullParser parser) throws InflateException {
506 final AttributeSet attrs = Xml.asAttributeSet(parser);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700507 final Resources res = context.getResources();
Jeff Sharkey3f177592009-05-18 15:23:12 -0700508
509 try {
510 int type;
511 while ((type = parser.next()) != XmlPullParser.START_TAG
512 && type != XmlPullParser.END_DOCUMENT) {
513 // Drain comments and whitespace
514 }
515
516 if (type != XmlPullParser.START_TAG) {
517 throw new InflateException("No start tag found");
518 }
519
520 if (!TAG_MAPPINGSET.equals(parser.getName())) {
521 throw new InflateException("Top level element must be MappingSet");
522 }
523
524 // Parse all children actions
525 final int depth = parser.getDepth();
526 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
527 && type != XmlPullParser.END_DOCUMENT) {
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700528 if (type == XmlPullParser.END_TAG) {
Jeff Sharkey3f177592009-05-18 15:23:12 -0700529 continue;
Dmitri Plotnikov3c690ce2009-05-19 14:43:45 -0700530 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700531
532 if (!TAG_MAPPING.equals(parser.getName())) {
533 throw new InflateException("Expected Mapping tag");
534 }
535
536 // Parse kind, mime-type, and RemoteViews reference
537 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Mapping);
538
539 Mapping mapping = new Mapping();
540 mapping.packageName = packageName;
541 mapping.mimeType = a.getString(R.styleable.Mapping_mimeType);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700542 mapping.summaryColumn = a.getString(R.styleable.Mapping_summaryColumn);
Jeff Sharkey39261272009-06-03 19:15:09 -0700543 mapping.detailColumn = a.getString(R.styleable.Mapping_detailColumn);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700544 mapping.remoteViewsRes = a.getResourceId(R.styleable.Mapping_remoteViews, -1);
545
546 // Read and resize icon if provided
547 int iconRes = a.getResourceId(R.styleable.Mapping_icon, -1);
548 if (iconRes != -1) {
Jeff Sharkey549aa162009-05-21 01:33:30 -0700549 mapping.icon = BitmapFactory.decodeResource(res, iconRes);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700550 }
551
552 addMapping(mapping);
553 Log.d(TAG, "Added mapping for packageName=" + mapping.packageName
554 + ", mimetype=" + mapping.mimeType);
555 }
556 } catch (XmlPullParserException e) {
557 throw new InflateException("Problem reading XML", e);
558 } catch (IOException e) {
559 throw new InflateException("Problem reading XML", e);
560 }
561 }
562 }
563
564 /**
565 * Borrowed from Launcher for {@link Bitmap} resizing.
566 */
567 static final class Utilities {
568 private static final Paint sPaint = new Paint();
569 private static final Rect sBounds = new Rect();
570 private static final Rect sOldBounds = new Rect();
571 private static Canvas sCanvas = new Canvas();
572
573 static {
574 sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
575 Paint.FILTER_BITMAP_FLAG));
576 }
577
578 /**
579 * Returns a Bitmap representing the thumbnail of the specified Bitmap.
580 * The size of the thumbnail is defined by the dimension
581 * android.R.dimen.launcher_application_icon_size. This method is not
582 * thread-safe and should be invoked on the UI thread only.
Jeff Sharkey170f7752009-08-16 00:12:48 -0700583 *
Jeff Sharkey3f177592009-05-18 15:23:12 -0700584 * @param bitmap The bitmap to get a thumbnail of.
585 * @param context The application's context.
586 * @return A thumbnail for the specified bitmap or the bitmap itself if
587 * the thumbnail could not be created.
Jeff Sharkey170f7752009-08-16 00:12:48 -0700588 * @deprecated use {@link Bitmap#createScaledBitmap} instead.
Jeff Sharkey3f177592009-05-18 15:23:12 -0700589 */
Jeff Sharkey170f7752009-08-16 00:12:48 -0700590 @Deprecated
Jeff Sharkey3f177592009-05-18 15:23:12 -0700591 static Bitmap createBitmapThumbnail(Bitmap bitmap, Context context, int size) {
592 int width = size;
593 int height = size;
594
595 final int bitmapWidth = bitmap.getWidth();
596 final int bitmapHeight = bitmap.getHeight();
597
598 if (width > 0 && height > 0 && (width < bitmapWidth || height < bitmapHeight)) {
599 final float ratio = (float)bitmapWidth / bitmapHeight;
600
601 if (bitmapWidth > bitmapHeight) {
602 height = (int)(width / ratio);
603 } else if (bitmapHeight > bitmapWidth) {
604 width = (int)(height * ratio);
605 }
606
607 final Bitmap.Config c = (width == size && height == size) ? bitmap.getConfig()
608 : Bitmap.Config.ARGB_8888;
609 final Bitmap thumb = Bitmap.createBitmap(size, size, c);
610 final Canvas canvas = sCanvas;
611 final Paint paint = sPaint;
612 canvas.setBitmap(thumb);
613 paint.setDither(false);
614 paint.setFilterBitmap(true);
615 sBounds.set((size - width) / 2, (size - height) / 2, width, height);
616 sOldBounds.set(0, 0, bitmapWidth, bitmapHeight);
617 canvas.drawBitmap(bitmap, sOldBounds, sBounds, paint);
618 return thumb;
619 }
620
621 return bitmap;
622 }
Dmitri Plotnikov06191cd2009-05-07 14:11:52 -0700623 }
624}