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