1 /* 2 * Copyright (C) 2010 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 package com.android.contacts.list; 17 18 import com.android.contacts.R; 19 import com.android.contacts.util.Constants; 20 21 import android.app.ActivityManager; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.res.Resources; 26 import android.database.Cursor; 27 import android.graphics.Bitmap; 28 import android.graphics.BitmapFactory; 29 import android.graphics.Canvas; 30 import android.graphics.Paint; 31 import android.graphics.Paint.FontMetricsInt; 32 import android.graphics.Rect; 33 import android.graphics.drawable.BitmapDrawable; 34 import android.graphics.drawable.Drawable; 35 import android.net.Uri; 36 import android.os.AsyncTask; 37 import android.provider.ContactsContract; 38 import android.provider.ContactsContract.CommonDataKinds.Phone; 39 import android.provider.ContactsContract.CommonDataKinds.Photo; 40 import android.provider.ContactsContract.Contacts; 41 import android.provider.ContactsContract.Data; 42 import android.text.TextPaint; 43 import android.text.TextUtils; 44 import android.text.TextUtils.TruncateAt; 45 46 /** 47 * Constructs shortcut intents. 48 */ 49 public class ShortcutIntentBuilder { 50 51 private static final String[] CONTACT_COLUMNS = { 52 Contacts.DISPLAY_NAME, 53 Contacts.PHOTO_ID, 54 }; 55 56 private static final int CONTACT_DISPLAY_NAME_COLUMN_INDEX = 0; 57 private static final int CONTACT_PHOTO_ID_COLUMN_INDEX = 1; 58 59 private static final String[] PHONE_COLUMNS = { 60 Phone.DISPLAY_NAME, 61 Phone.PHOTO_ID, 62 Phone.NUMBER, 63 Phone.TYPE, 64 Phone.LABEL 65 }; 66 67 private static final int PHONE_DISPLAY_NAME_COLUMN_INDEX = 0; 68 private static final int PHONE_PHOTO_ID_COLUMN_INDEX = 1; 69 private static final int PHONE_NUMBER_COLUMN_INDEX = 2; 70 private static final int PHONE_TYPE_COLUMN_INDEX = 3; 71 private static final int PHONE_LABEL_COLUMN_INDEX = 4; 72 73 private static final String[] PHOTO_COLUMNS = { 74 Photo.PHOTO, 75 }; 76 77 private static final int PHOTO_PHOTO_COLUMN_INDEX = 0; 78 79 private static final String PHOTO_SELECTION = Photo._ID + "=?"; 80 81 private final OnShortcutIntentCreatedListener mListener; 82 private final Context mContext; 83 private int mIconSize; 84 private final int mIconDensity; 85 private final int mBorderWidth; 86 private final int mBorderColor; 87 88 /** 89 * Listener interface. 90 */ 91 public interface OnShortcutIntentCreatedListener { 92 93 /** 94 * Callback for shortcut intent creation. 95 * 96 * @param uri the original URI for which the shortcut intent has been 97 * created. 98 * @param shortcutIntent resulting shortcut intent. 99 */ 100 void onShortcutIntentCreated(Uri uri, Intent shortcutIntent); 101 } 102 103 public ShortcutIntentBuilder(Context context, OnShortcutIntentCreatedListener listener) { 104 mContext = context; 105 mListener = listener; 106 107 final Resources r = context.getResources(); 108 final ActivityManager am = (ActivityManager) context 109 .getSystemService(Context.ACTIVITY_SERVICE); 110 mIconSize = r.getDimensionPixelSize(R.dimen.shortcut_icon_size); 111 if (mIconSize == 0) { 112 mIconSize = am.getLauncherLargeIconSize(); 113 } 114 mIconDensity = am.getLauncherLargeIconDensity(); 115 mBorderWidth = r.getDimensionPixelOffset( 116 R.dimen.shortcut_icon_border_width); 117 mBorderColor = r.getColor(R.color.shortcut_overlay_text_background); 118 } 119 120 public void createContactShortcutIntent(Uri contactUri) { 121 new ContactLoadingAsyncTask(contactUri).execute(); 122 } 123 124 public void createPhoneNumberShortcutIntent(Uri dataUri, String shortcutAction) { 125 new PhoneNumberLoadingAsyncTask(dataUri, shortcutAction).execute(); 126 } 127 128 /** 129 * An asynchronous task that loads name, photo and other data from the database. 130 */ 131 private abstract class LoadingAsyncTask extends AsyncTask<Void, Void, Void> { 132 protected Uri mUri; 133 protected String mDisplayName; 134 protected byte[] mBitmapData; 135 protected long mPhotoId; 136 137 public LoadingAsyncTask(Uri uri) { 138 mUri = uri; 139 } 140 141 @Override 142 protected Void doInBackground(Void... params) { 143 loadData(); 144 loadPhoto(); 145 return null; 146 } 147 148 protected abstract void loadData(); 149 150 private void loadPhoto() { 151 if (mPhotoId == 0) { 152 return; 153 } 154 155 ContentResolver resolver = mContext.getContentResolver(); 156 Cursor cursor = resolver.query(Data.CONTENT_URI, PHOTO_COLUMNS, PHOTO_SELECTION, 157 new String[] { String.valueOf(mPhotoId) }, null); 158 if (cursor != null) { 159 try { 160 if (cursor.moveToFirst()) { 161 mBitmapData = cursor.getBlob(PHOTO_PHOTO_COLUMN_INDEX); 162 } 163 } finally { 164 cursor.close(); 165 } 166 } 167 } 168 } 169 170 private final class ContactLoadingAsyncTask extends LoadingAsyncTask { 171 public ContactLoadingAsyncTask(Uri uri) { 172 super(uri); 173 } 174 175 @Override 176 protected void loadData() { 177 ContentResolver resolver = mContext.getContentResolver(); 178 Cursor cursor = resolver.query(mUri, CONTACT_COLUMNS, null, null, null); 179 if (cursor != null) { 180 try { 181 if (cursor.moveToFirst()) { 182 mDisplayName = cursor.getString(CONTACT_DISPLAY_NAME_COLUMN_INDEX); 183 mPhotoId = cursor.getLong(CONTACT_PHOTO_ID_COLUMN_INDEX); 184 } 185 } finally { 186 cursor.close(); 187 } 188 } 189 } 190 @Override 191 protected void onPostExecute(Void result) { 192 createContactShortcutIntent(mUri, mDisplayName, mBitmapData); 193 } 194 } 195 196 private final class PhoneNumberLoadingAsyncTask extends LoadingAsyncTask { 197 private final String mShortcutAction; 198 private String mPhoneNumber; 199 private int mPhoneType; 200 private String mPhoneLabel; 201 202 public PhoneNumberLoadingAsyncTask(Uri uri, String shortcutAction) { 203 super(uri); 204 mShortcutAction = shortcutAction; 205 } 206 207 @Override 208 protected void loadData() { 209 ContentResolver resolver = mContext.getContentResolver(); 210 Cursor cursor = resolver.query(mUri, PHONE_COLUMNS, null, null, null); 211 if (cursor != null) { 212 try { 213 if (cursor.moveToFirst()) { 214 mDisplayName = cursor.getString(PHONE_DISPLAY_NAME_COLUMN_INDEX); 215 mPhotoId = cursor.getLong(PHONE_PHOTO_ID_COLUMN_INDEX); 216 mPhoneNumber = cursor.getString(PHONE_NUMBER_COLUMN_INDEX); 217 mPhoneType = cursor.getInt(PHONE_TYPE_COLUMN_INDEX); 218 mPhoneLabel = cursor.getString(PHONE_LABEL_COLUMN_INDEX); 219 } 220 } finally { 221 cursor.close(); 222 } 223 } 224 } 225 226 @Override 227 protected void onPostExecute(Void result) { 228 createPhoneNumberShortcutIntent(mUri, mDisplayName, mBitmapData, mPhoneNumber, 229 mPhoneType, mPhoneLabel, mShortcutAction); 230 } 231 } 232 233 private Bitmap getPhotoBitmap(byte[] bitmapData) { 234 Bitmap bitmap; 235 if (bitmapData != null) { 236 bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length, null); 237 } else { 238 bitmap = ((BitmapDrawable) mContext.getResources().getDrawableForDensity( 239 R.drawable.ic_contact_picture_holo_light, mIconDensity)).getBitmap(); 240 } 241 return bitmap; 242 } 243 244 private void createContactShortcutIntent(Uri contactUri, String displayName, 245 byte[] bitmapData) { 246 Bitmap bitmap = getPhotoBitmap(bitmapData); 247 248 Intent shortcutIntent; 249 // This is a simple shortcut to view a contact. 250 shortcutIntent = new Intent(ContactsContract.QuickContact.ACTION_QUICK_CONTACT); 251 shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 252 Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 253 254 shortcutIntent.setData(contactUri); 255 shortcutIntent.putExtra(ContactsContract.QuickContact.EXTRA_MODE, 256 ContactsContract.QuickContact.MODE_LARGE); 257 shortcutIntent.putExtra(ContactsContract.QuickContact.EXTRA_EXCLUDE_MIMES, 258 (String[]) null); 259 shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 260 261 final Bitmap icon = generateQuickContactIcon(bitmap); 262 263 Intent intent = new Intent(); 264 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon); 265 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); 266 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, displayName); 267 268 mListener.onShortcutIntentCreated(contactUri, intent); 269 } 270 271 private void createPhoneNumberShortcutIntent(Uri uri, String displayName, byte[] bitmapData, 272 String phoneNumber, int phoneType, String phoneLabel, String shortcutAction) { 273 Bitmap bitmap = getPhotoBitmap(bitmapData); 274 275 Uri phoneUri; 276 if (Intent.ACTION_CALL.equals(shortcutAction)) { 277 // Make the URI a direct tel: URI so that it will always continue to work 278 phoneUri = Uri.fromParts(Constants.SCHEME_TEL, phoneNumber, null); 279 bitmap = generatePhoneNumberIcon(bitmap, phoneType, phoneLabel, 280 R.drawable.badge_action_call); 281 } else { 282 phoneUri = Uri.fromParts(Constants.SCHEME_SMSTO, phoneNumber, null); 283 bitmap = generatePhoneNumberIcon(bitmap, phoneType, phoneLabel, 284 R.drawable.badge_action_sms); 285 } 286 287 Intent shortcutIntent = new Intent(shortcutAction, phoneUri); 288 shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 289 290 Intent intent = new Intent(); 291 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap); 292 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); 293 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, displayName); 294 295 mListener.onShortcutIntentCreated(uri, intent); 296 } 297 298 private void drawBorder(Canvas canvas, Rect dst) { 299 // Darken the border 300 final Paint workPaint = new Paint(); 301 workPaint.setColor(mBorderColor); 302 workPaint.setStyle(Paint.Style.STROKE); 303 // The stroke is drawn centered on the rect bounds, and since half will be drawn outside the 304 // bounds, we need to double the width for it to appear as intended. 305 workPaint.setStrokeWidth(mBorderWidth * 2); 306 canvas.drawRect(dst, workPaint); 307 } 308 309 private Bitmap generateQuickContactIcon(Bitmap photo) { 310 311 // Setup the drawing classes 312 Bitmap icon = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888); 313 Canvas canvas = new Canvas(icon); 314 315 // Copy in the photo 316 Paint photoPaint = new Paint(); 317 photoPaint.setDither(true); 318 photoPaint.setFilterBitmap(true); 319 Rect src = new Rect(0,0, photo.getWidth(),photo.getHeight()); 320 Rect dst = new Rect(0,0, mIconSize, mIconSize); 321 canvas.drawBitmap(photo, src, dst, photoPaint); 322 323 drawBorder(canvas, dst); 324 325 Drawable overlay = mContext.getResources().getDrawableForDensity( 326 com.android.internal.R.drawable.quickcontact_badge_overlay_dark, mIconDensity); 327 328 overlay.setBounds(dst); 329 overlay.draw(canvas); 330 canvas.setBitmap(null); 331 332 return icon; 333 } 334 335 /** 336 * Generates a phone number shortcut icon. Adds an overlay describing the type of the phone 337 * number, and if there is a photo also adds the call action icon. 338 */ 339 private Bitmap generatePhoneNumberIcon(Bitmap photo, int phoneType, String phoneLabel, 340 int actionResId) { 341 final Resources r = mContext.getResources(); 342 final float density = r.getDisplayMetrics().density; 343 344 Bitmap phoneIcon = ((BitmapDrawable) r.getDrawableForDensity(actionResId, mIconDensity)) 345 .getBitmap(); 346 347 // Setup the drawing classes 348 Bitmap icon = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888); 349 Canvas canvas = new Canvas(icon); 350 351 // Copy in the photo 352 Paint photoPaint = new Paint(); 353 photoPaint.setDither(true); 354 photoPaint.setFilterBitmap(true); 355 Rect src = new Rect(0, 0, photo.getWidth(), photo.getHeight()); 356 Rect dst = new Rect(0, 0, mIconSize, mIconSize); 357 canvas.drawBitmap(photo, src, dst, photoPaint); 358 359 drawBorder(canvas, dst); 360 361 // Create an overlay for the phone number type 362 CharSequence overlay = Phone.getTypeLabel(r, phoneType, phoneLabel); 363 364 if (overlay != null) { 365 TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); 366 textPaint.setTextSize(r.getDimension(R.dimen.shortcut_overlay_text_size)); 367 textPaint.setColor(r.getColor(R.color.textColorIconOverlay)); 368 textPaint.setShadowLayer(4f, 0, 2f, r.getColor(R.color.textColorIconOverlayShadow)); 369 370 final FontMetricsInt fmi = textPaint.getFontMetricsInt(); 371 372 // First fill in a darker background around the text to be drawn 373 final Paint workPaint = new Paint(); 374 workPaint.setColor(mBorderColor); 375 workPaint.setStyle(Paint.Style.FILL); 376 final int textPadding = r 377 .getDimensionPixelOffset(R.dimen.shortcut_overlay_text_background_padding); 378 final int textBandHeight = (fmi.descent - fmi.ascent) + textPadding * 2; 379 dst.set(0 + mBorderWidth, mIconSize - textBandHeight, mIconSize - mBorderWidth, 380 mIconSize - mBorderWidth); 381 canvas.drawRect(dst, workPaint); 382 383 final float sidePadding = mBorderWidth; 384 overlay = TextUtils.ellipsize(overlay, textPaint, mIconSize - 2 * sidePadding, 385 TruncateAt.END_SMALL); 386 final float textWidth = textPaint.measureText(overlay, 0, overlay.length()); 387 canvas.drawText(overlay, 0, overlay.length(), (mIconSize - textWidth) / 2, mIconSize 388 - fmi.descent - textPadding, textPaint); 389 } 390 391 // Draw the phone action icon as an overlay 392 src.set(0, 0, phoneIcon.getWidth(), phoneIcon.getHeight()); 393 int iconWidth = icon.getWidth(); 394 dst.set(iconWidth - ((int) (20 * density)), -1, 395 iconWidth, ((int) (19 * density))); 396 dst.offset(-mBorderWidth, mBorderWidth); 397 canvas.drawBitmap(phoneIcon, src, dst, photoPaint); 398 399 canvas.setBitmap(null); 400 401 return icon; 402 } 403 } 404