Home | History | Annotate | Download | only in list
      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