Making call/sms shortcuts work with Loaders and Fragments.

Change-Id: Ie88fb17e0e533e8868c68f8e74d609df17310e35
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index a8554b3..512b182 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -37,7 +37,6 @@
 import com.android.contacts.ui.ContactsPreferencesActivity;
 import com.android.contacts.ui.ContactsPreferencesActivity.Prefs;
 import com.android.contacts.util.AccountSelectionUtil;
-import com.android.contacts.util.Constants;
 import com.android.contacts.widget.ContextMenuAdapter;
 
 import android.accounts.Account;
@@ -59,14 +58,6 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.database.MatrixCursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -86,7 +77,6 @@
 import android.provider.ContactsContract.SearchSnippetColumns;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.Intents.Insert;
 import android.telephony.TelephonyManager;
@@ -101,7 +91,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.View.OnClickListener;
-import android.view.inputmethod.InputMethodManager;
 import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.ListAdapter;
@@ -112,7 +101,6 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Random;
 
 /**
  * Displays a list of contacts. Usually is embedded into the ContactsActivity.
@@ -701,6 +689,7 @@
                     fragment.setLegacyCompatibility(true);
                 }
                 fragment.setSectionHeaderDisplayEnabled(false);
+                fragment.setShortcutAction(mShortcutAction);
                 fragment.setOnPhoneNumberPickerActionListener(
                         new OnPhoneNumberPickerActionListener() {
 
@@ -713,6 +702,11 @@
                     public void onSearchAllContactsAction(String string) {
                         doSearch();
                     }
+
+                    public void onShortcutIntentCreated(Intent intent) {
+                        setResult(RESULT_OK, intent);
+                        finish();
+                    }
                 });
                 mListFragment = fragment;
                 break;
@@ -1495,221 +1489,8 @@
         }
     }
 
-    private void hideSoftKeyboard() {
-        // Hide soft keyboard, if visible
-        InputMethodManager inputMethodManager = (InputMethodManager)
-                getSystemService(Context.INPUT_METHOD_SERVICE);
-        inputMethodManager.hideSoftInputFromWindow(mListView.getWindowToken(), 0);
-    }
-
-    /**
-     * @param selectedUri In most cases, this should be a lookup {@link Uri}, possibly
-     *            generated through {@link Contacts#getLookupUri(long, String)}.
-     */
-    protected void returnPickerResult(Cursor c, String name, Uri selectedUri) {
-        final Intent intent = new Intent();
-
-        if (mShortcutAction != null) {
-            Intent shortcutIntent;
-            if (Intent.ACTION_VIEW.equals(mShortcutAction)) {
-                // This is a simple shortcut to view a contact.
-                shortcutIntent = new Intent(ContactsContract.QuickContact.ACTION_QUICK_CONTACT);
-                shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
-                        Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-
-                shortcutIntent.setData(selectedUri);
-                shortcutIntent.putExtra(ContactsContract.QuickContact.EXTRA_MODE,
-                        ContactsContract.QuickContact.MODE_LARGE);
-                shortcutIntent.putExtra(ContactsContract.QuickContact.EXTRA_EXCLUDE_MIMES,
-                        (String[]) null);
-
-                final Bitmap icon = framePhoto(loadContactPhoto(selectedUri, null));
-                if (icon != null) {
-                    intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, scaleToAppIconSize(icon));
-                } else {
-                    intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
-                            Intent.ShortcutIconResource.fromContext(this,
-                                    R.drawable.ic_launcher_shortcut_contact));
-                }
-            } else {
-                // This is a direct dial or sms shortcut.
-                String number = c.getString(PHONE_NUMBER_COLUMN_INDEX);
-                int type = c.getInt(PHONE_TYPE_COLUMN_INDEX);
-                String scheme;
-                int resid;
-                if (Intent.ACTION_CALL.equals(mShortcutAction)) {
-                    scheme = Constants.SCHEME_TEL;
-                    resid = R.drawable.badge_action_call;
-                } else {
-                    scheme = Constants.SCHEME_SMSTO;
-                    resid = R.drawable.badge_action_sms;
-                }
-
-                // Make the URI a direct tel: URI so that it will always continue to work
-                Uri phoneUri = Uri.fromParts(scheme, number, null);
-                shortcutIntent = new Intent(mShortcutAction, phoneUri);
-
-                intent.putExtra(Intent.EXTRA_SHORTCUT_ICON,
-                        generatePhoneNumberIcon(selectedUri, type, resid));
-            }
-            shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
-            intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
-            setResult(RESULT_OK, intent);
-        } else {
-            intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
-            setResult(RESULT_OK, intent.setData(selectedUri));
-        }
-        finish();
-    }
-
-    private Bitmap framePhoto(Bitmap photo) {
-        final Resources r = getResources();
-        final Drawable frame = r.getDrawable(com.android.internal.R.drawable.quickcontact_badge);
-
-        final int width = r.getDimensionPixelSize(R.dimen.contact_shortcut_frame_width);
-        final int height = r.getDimensionPixelSize(R.dimen.contact_shortcut_frame_height);
-
-        frame.setBounds(0, 0, width, height);
-
-        final Rect padding = new Rect();
-        frame.getPadding(padding);
-
-        final Rect source = new Rect(0, 0, photo.getWidth(), photo.getHeight());
-        final Rect destination = new Rect(padding.left, padding.top,
-                width - padding.right, height - padding.bottom);
-
-        final int d = Math.max(width, height);
-        final Bitmap b = Bitmap.createBitmap(d, d, Bitmap.Config.ARGB_8888);
-        final Canvas c = new Canvas(b);
-
-        c.translate((d - width) / 2.0f, (d - height) / 2.0f);
-        frame.draw(c);
-        c.drawBitmap(photo, source, destination, new Paint(Paint.FILTER_BITMAP_FLAG));
-
-        return b;
-    }
-
-    /**
-     * Generates a phone number shortcut icon. Adds an overlay describing the type of the phone
-     * number, and if there is a photo also adds the call action icon.
-     *
-     * @param lookupUri The person the phone number belongs to
-     * @param type The type of the phone number
-     * @param actionResId The ID for the action resource
-     * @return The bitmap for the icon
-     */
-    private Bitmap generatePhoneNumberIcon(Uri lookupUri, int type, int actionResId) {
-        final Resources r = getResources();
-        boolean drawPhoneOverlay = true;
-        final float scaleDensity = getResources().getDisplayMetrics().scaledDensity;
-
-        Bitmap photo = loadContactPhoto(lookupUri, null);
-        if (photo == null) {
-            // If there isn't a photo use the generic phone action icon instead
-            Bitmap phoneIcon = getPhoneActionIcon(r, actionResId);
-            if (phoneIcon != null) {
-                photo = phoneIcon;
-                drawPhoneOverlay = false;
-            } else {
-                return null;
-            }
-        }
-
-        // Setup the drawing classes
-        Bitmap icon = createShortcutBitmap();
-        Canvas canvas = new Canvas(icon);
-
-        // Copy in the photo
-        Paint photoPaint = new Paint();
-        photoPaint.setDither(true);
-        photoPaint.setFilterBitmap(true);
-        Rect src = new Rect(0,0, photo.getWidth(),photo.getHeight());
-        Rect dst = new Rect(0,0, mIconSize, mIconSize);
-        canvas.drawBitmap(photo, src, dst, photoPaint);
-
-        // Create an overlay for the phone number type
-        String overlay = null;
-        switch (type) {
-            case Phone.TYPE_HOME:
-                overlay = getString(R.string.type_short_home);
-                break;
-
-            case Phone.TYPE_MOBILE:
-                overlay = getString(R.string.type_short_mobile);
-                break;
-
-            case Phone.TYPE_WORK:
-                overlay = getString(R.string.type_short_work);
-                break;
-
-            case Phone.TYPE_PAGER:
-                overlay = getString(R.string.type_short_pager);
-                break;
-
-            case Phone.TYPE_OTHER:
-                overlay = getString(R.string.type_short_other);
-                break;
-        }
-        if (overlay != null) {
-            Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
-            textPaint.setTextSize(20.0f * scaleDensity);
-            textPaint.setTypeface(Typeface.DEFAULT_BOLD);
-            textPaint.setColor(r.getColor(R.color.textColorIconOverlay));
-            textPaint.setShadowLayer(3f, 1, 1, r.getColor(R.color.textColorIconOverlayShadow));
-            canvas.drawText(overlay, 2 * scaleDensity, 16 * scaleDensity, textPaint);
-        }
-
-        // Draw the phone action icon as an overlay
-        if (ENABLE_ACTION_ICON_OVERLAYS && drawPhoneOverlay) {
-            Bitmap phoneIcon = getPhoneActionIcon(r, actionResId);
-            if (phoneIcon != null) {
-                src.set(0, 0, phoneIcon.getWidth(), phoneIcon.getHeight());
-                int iconWidth = icon.getWidth();
-                dst.set(iconWidth - ((int) (20 * scaleDensity)), -1,
-                        iconWidth, ((int) (19 * scaleDensity)));
-                canvas.drawBitmap(phoneIcon, src, dst, photoPaint);
-            }
-        }
-
-        return icon;
-    }
-
-    private Bitmap scaleToAppIconSize(Bitmap photo) {
-        // Setup the drawing classes
-        Bitmap icon = createShortcutBitmap();
-        Canvas canvas = new Canvas(icon);
-
-        // Copy in the photo
-        Paint photoPaint = new Paint();
-        photoPaint.setDither(true);
-        photoPaint.setFilterBitmap(true);
-        Rect src = new Rect(0,0, photo.getWidth(),photo.getHeight());
-        Rect dst = new Rect(0,0, mIconSize, mIconSize);
-        canvas.drawBitmap(photo, src, dst, photoPaint);
-
-        return icon;
-    }
-
-    private Bitmap createShortcutBitmap() {
-        return Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
-    }
-
-    /**
-     * Returns the icon for the phone call action.
-     *
-     * @param r The resources to load the icon from
-     * @param resId The resource ID to load
-     * @return the icon for the phone call action
-     */
-    private Bitmap getPhoneActionIcon(Resources r, int resId) {
-        Drawable phoneIcon = r.getDrawable(resId);
-        if (phoneIcon instanceof BitmapDrawable) {
-            BitmapDrawable bd = (BitmapDrawable) phoneIcon;
-            return bd.getBitmap();
-        } else {
-            return null;
-        }
+    @Deprecated
+    private void returnPickerResult(Cursor c, String string, Uri uri) {
     }
 
     protected Uri getUriToQuery() {
@@ -1889,54 +1670,6 @@
         return CONTACTS_SUMMARY_PROJECTION;
     }
 
-    private Bitmap loadContactPhoto(Uri selectedUri, BitmapFactory.Options options) {
-        Uri contactUri = null;
-        if (Contacts.CONTENT_ITEM_TYPE.equals(getContentResolver().getType(selectedUri))) {
-            // TODO we should have a "photo" directory under the lookup URI itself
-            contactUri = Contacts.lookupContact(getContentResolver(), selectedUri);
-        } else {
-
-            Cursor cursor = getContentResolver().query(selectedUri,
-                    new String[] { Data.CONTACT_ID }, null, null, null);
-            try {
-                if (cursor != null && cursor.moveToFirst()) {
-                    final long contactId = cursor.getLong(0);
-                    contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
-                }
-            } finally {
-                if (cursor != null) cursor.close();
-            }
-        }
-
-        Cursor cursor = null;
-        Bitmap bm = null;
-
-        try {
-            Uri photoUri = Uri.withAppendedPath(contactUri, Contacts.Photo.CONTENT_DIRECTORY);
-            cursor = getContentResolver().query(photoUri, new String[] {Photo.PHOTO},
-                    null, null, null);
-            if (cursor != null && cursor.moveToFirst()) {
-                bm = ContactsUtils.loadContactPhoto(cursor, 0, options);
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-
-        if (bm == null) {
-            final int[] fallbacks = {
-                R.drawable.ic_contact_picture,
-                R.drawable.ic_contact_picture_2,
-                R.drawable.ic_contact_picture_3
-            };
-            bm = BitmapFactory.decodeResource(getResources(),
-                    fallbacks[new Random().nextInt(fallbacks.length)]);
-        }
-
-        return bm;
-    }
-
     /**
      * Return the selection arguments for a default query based on the
      * {@link #mDisplayOnlyPhones} flag.
diff --git a/src/com/android/contacts/JoinContactActivity.java b/src/com/android/contacts/JoinContactActivity.java
index 21f5929..9024e0b 100644
--- a/src/com/android/contacts/JoinContactActivity.java
+++ b/src/com/android/contacts/JoinContactActivity.java
@@ -19,6 +19,7 @@
 
 import com.android.contacts.list.JoinContactListAdapter;
 import com.android.contacts.list.JoinContactListFragment;
+import com.android.internal.telephony.gsm.stk.ResultCode;
 
 import android.content.ContentUris;
 import android.content.Intent;
@@ -110,7 +111,8 @@
             startQuery();
         } else {
             final Uri uri = getSelectedUri(position);
-            returnPickerResult(null, null, uri);
+            setResult(RESULT_OK, new Intent(null, uri));
+            finish();
         }
     }
 
diff --git a/src/com/android/contacts/list/OnPhoneNumberPickerActionListener.java b/src/com/android/contacts/list/OnPhoneNumberPickerActionListener.java
index f30c223..701cc78 100644
--- a/src/com/android/contacts/list/OnPhoneNumberPickerActionListener.java
+++ b/src/com/android/contacts/list/OnPhoneNumberPickerActionListener.java
@@ -15,6 +15,7 @@
  */
 package com.android.contacts.list;
 
+import android.content.Intent;
 import android.net.Uri;
 
 /**
@@ -28,6 +29,11 @@
     void onPickPhoneNumberAction(Uri dataUri);
 
     /**
+     * Returns the selected number as a shortcut intent.
+     */
+    void onShortcutIntentCreated(Intent intent);
+
+    /**
      * Searches all contacts for the specified string an show results for browsing.
      */
     void onSearchAllContactsAction(String string);
diff --git a/src/com/android/contacts/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
index 588f4ac..f155637 100644
--- a/src/com/android/contacts/list/PhoneNumberPickerFragment.java
+++ b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
@@ -16,7 +16,9 @@
 package com.android.contacts.list;
 
 import com.android.contacts.R;
+import com.android.contacts.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
 
+import android.content.Intent;
 import android.net.Uri;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -26,8 +28,10 @@
  * Fragment containing a contact list used for browsing (as compared to
  * picking a contact with one of the PICK intents).
  */
-public class PhoneNumberPickerFragment extends ContactEntryListFragment<PhoneNumberListAdapter> {
+public class PhoneNumberPickerFragment extends ContactEntryListFragment<PhoneNumberListAdapter>
+        implements OnShortcutIntentCreatedListener {
     private OnPhoneNumberPickerActionListener mListener;
+    private String mShortcutAction;
 
     public PhoneNumberPickerFragment() {
         setPhotoLoaderEnabled(true);
@@ -38,6 +42,14 @@
         this.mListener = listener;
     }
 
+    /**
+     * @param shortcutAction either {@link Intent#ACTION_CALL} or
+     *            {@link Intent#ACTION_SENDTO} or null.
+     */
+    public void setShortcutAction(String shortcutAction) {
+        this.mShortcutAction = shortcutAction;
+    }
+
     @Override
     protected void onItemClick(int position, long id) {
         PhoneNumberListAdapter adapter = getAdapter();
@@ -78,6 +90,15 @@
     }
 
     public void pickPhoneNumber(Uri uri) {
-        mListener.onPickPhoneNumberAction(uri);
+        if (mShortcutAction == null) {
+            mListener.onPickPhoneNumberAction(uri);
+        } else {
+            ShortcutIntentBuilder builder = new ShortcutIntentBuilder(getActivity(), this);
+            builder.createPhoneNumberShortcutIntent(getAdapter().getDataUri(), mShortcutAction);
+        }
+    }
+
+    public void onShortcutIntentCreated(Uri uri, Intent shortcutIntent) {
+        mListener.onShortcutIntentCreated(shortcutIntent);
     }
 }
diff --git a/src/com/android/contacts/list/ShortcutIntentBuilder.java b/src/com/android/contacts/list/ShortcutIntentBuilder.java
index 6452c05..7a4f9de 100644
--- a/src/com/android/contacts/list/ShortcutIntentBuilder.java
+++ b/src/com/android/contacts/list/ShortcutIntentBuilder.java
@@ -15,11 +15,10 @@
  */
 package com.android.contacts.list;
 
-import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
+import com.android.contacts.util.Constants;
 
 import android.content.ContentResolver;
-import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -29,13 +28,16 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
 
 import java.util.Random;
 
@@ -52,6 +54,18 @@
     private static final int CONTACT_DISPLAY_NAME_COLUMN_INDEX = 0;
     private static final int CONTACT_PHOTO_ID_COLUMN_INDEX = 1;
 
+    private static final String[] PHONE_COLUMNS = {
+        Phone.DISPLAY_NAME,
+        Phone.PHOTO_ID,
+        Phone.NUMBER,
+        Phone.TYPE,
+    };
+
+    private static final int PHONE_DISPLAY_NAME_COLUMN_INDEX = 0;
+    private static final int PHONE_PHOTO_ID_COLUMN_INDEX = 1;
+    private static final int PHONE_NUMBER_COLUMN_INDEX = 2;
+    private static final int PHONE_TYPE_COLUMN_INDEX = 3;
+
     private static final String[] PHOTO_COLUMNS = {
         Photo.PHOTO,
     };
@@ -87,64 +101,119 @@
     }
 
     public void createContactShortcutIntent(Uri contactUri) {
-        new ContactLoadingAsyncTask().execute(contactUri);
+        new ContactLoadingAsyncTask(contactUri).execute();
+    }
+
+    public void createPhoneNumberShortcutIntent(Uri dataUri, String shortcutAction) {
+        new PhoneNumberLoadingAsyncTask(dataUri, shortcutAction).execute();
     }
 
     /**
-     * An asynchronous task that loads the photo from the database.
+     * An asynchronous task that loads name, photo and other data from the database.
      */
-    private final class ContactLoadingAsyncTask extends AsyncTask<Uri, Void, Void> {
-        private Uri mUri;
-        private String mDisplayName;
-        private byte[] mBitmapData;
+    private abstract class LoadingAsyncTask extends AsyncTask<Void, Void, Void> {
+        protected Uri mUri;
+        protected String mDisplayName;
+        protected byte[] mBitmapData;
+        protected long mPhotoId;
+
+        public LoadingAsyncTask(Uri uri) {
+            mUri = uri;
+        }
 
         @Override
-        protected Void doInBackground(Uri... uris) {
-            mUri = uris[0];
+        protected Void doInBackground(Void... params) {
+            loadData();
+            loadPhoto();
+            return null;
+        }
+
+        protected abstract void loadData();
+
+        private void loadPhoto() {
+            if (mPhotoId == 0) {
+                return;
+            }
 
             ContentResolver resolver = mContext.getContentResolver();
-
-            long photoId = 0;
-
-            Cursor cursor = resolver.query(mUri, CONTACT_COLUMNS, null, null, null);
+            Cursor cursor = resolver.query(Data.CONTENT_URI, PHOTO_COLUMNS, PHOTO_SELECTION,
+                    new String[] { String.valueOf(mPhotoId) }, null);
             if (cursor != null) {
                 try {
                     if (cursor.moveToFirst()) {
-                        mDisplayName = cursor.getString(CONTACT_DISPLAY_NAME_COLUMN_INDEX);
-                        photoId = cursor.getLong(CONTACT_PHOTO_ID_COLUMN_INDEX);
+                        mBitmapData = cursor.getBlob(PHOTO_PHOTO_COLUMN_INDEX);
                     }
                 } finally {
                     cursor.close();
                 }
             }
+        }
+    }
 
-            if (photoId != 0) {
-                cursor = resolver.query(Data.CONTENT_URI, PHOTO_COLUMNS, PHOTO_SELECTION,
-                        new String[] { String.valueOf(photoId) }, null);
-                if (cursor != null) {
-                    try {
-                        if (cursor.moveToFirst()) {
-                            mBitmapData = cursor.getBlob(PHOTO_PHOTO_COLUMN_INDEX);
-                        }
-                    } finally {
-                        cursor.close();
-                    }
-                }
-            }
-
-            return null;
+    private final class ContactLoadingAsyncTask extends LoadingAsyncTask {
+        public ContactLoadingAsyncTask(Uri uri) {
+            super(uri);
         }
 
         @Override
+        protected void loadData() {
+            ContentResolver resolver = mContext.getContentResolver();
+            Cursor cursor = resolver.query(mUri, CONTACT_COLUMNS, null, null, null);
+            if (cursor != null) {
+                try {
+                    if (cursor.moveToFirst()) {
+                        mDisplayName = cursor.getString(CONTACT_DISPLAY_NAME_COLUMN_INDEX);
+                        mPhotoId = cursor.getLong(CONTACT_PHOTO_ID_COLUMN_INDEX);
+                    }
+                } finally {
+                    cursor.close();
+                }
+            }
+        }
+        @Override
         protected void onPostExecute(Void result) {
             createContactShortcutIntent(mUri, mDisplayName, mBitmapData);
         }
     }
 
-    public void createContactShortcutIntent(Uri contactUri, String displayName, byte[] bitmapData) {
+    private final class PhoneNumberLoadingAsyncTask extends LoadingAsyncTask {
+        private final String mShortcutAction;
+        private String mPhoneNumber;
+        private int mPhoneType;
 
+        public PhoneNumberLoadingAsyncTask(Uri uri, String shortcutAction) {
+            super(uri);
+            mShortcutAction = shortcutAction;
+        }
+
+        @Override
+        protected void loadData() {
+            ContentResolver resolver = mContext.getContentResolver();
+            Cursor cursor = resolver.query(mUri, PHONE_COLUMNS, null, null, null);
+            if (cursor != null) {
+                try {
+                    if (cursor.moveToFirst()) {
+                        mDisplayName = cursor.getString(PHONE_DISPLAY_NAME_COLUMN_INDEX);
+                        mPhotoId = cursor.getLong(PHONE_PHOTO_ID_COLUMN_INDEX);
+                        mPhoneNumber = cursor.getString(PHONE_NUMBER_COLUMN_INDEX);
+                        mPhoneType = cursor.getInt(PHONE_TYPE_COLUMN_INDEX);
+                    }
+                } finally {
+                    cursor.close();
+                }
+            }
+        }
+
+        @Override
+        protected void onPostExecute(Void result) {
+            createPhoneNumberShortcutIntent(mUri, mDisplayName, mBitmapData, mPhoneNumber,
+                    mPhoneType, mShortcutAction);
+        }
+    }
+
+    private void createContactShortcutIntent(Uri contactUri, String displayName,
+            byte[] bitmapData) {
         Bitmap bitmap;
-
         if (bitmapData != null) {
             bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length, null);
         } else {
@@ -170,16 +239,44 @@
                 (String[]) null);
         shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
 
-        Intent intent = new Intent();
+        final Bitmap icon = scaleToAppIconSize(framePhoto(bitmap));
 
-        final Bitmap icon = framePhoto(bitmap);
-        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, scaleToAppIconSize(icon));
+        Intent intent = new Intent();
+        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
         intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
         intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, displayName);
 
         mListener.onShortcutIntentCreated(contactUri, intent);
     }
 
+    private void createPhoneNumberShortcutIntent(Uri uri, String displayName, byte[] bitmapData,
+            String phoneNumber, int phoneType, String shortcutAction) {
+        Bitmap bitmap = null;
+        if (bitmapData != null) {
+            bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length, null);
+        }
+
+        Uri phoneUri;
+        if (Intent.ACTION_CALL.equals(shortcutAction)) {
+            // Make the URI a direct tel: URI so that it will always continue to work
+            phoneUri = Uri.fromParts(Constants.SCHEME_TEL, phoneNumber, null);
+            bitmap = generatePhoneNumberIcon(bitmap, phoneType, R.drawable.badge_action_call);
+        } else {
+            phoneUri = Uri.fromParts(Constants.SCHEME_SMSTO, phoneNumber, null);
+            bitmap = generatePhoneNumberIcon(bitmap, phoneType, R.drawable.badge_action_sms);
+        }
+
+        Intent shortcutIntent = new Intent(shortcutAction, phoneUri);
+        shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
+        Intent intent = new Intent();
+        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
+        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
+        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, displayName);
+
+        mListener.onShortcutIntentCreated(uri, intent);
+    }
+
     private Bitmap framePhoto(Bitmap photo) {
         final Resources r = mContext.getResources();
         final Drawable frame = r.getDrawable(com.android.internal.R.drawable.quickcontact_badge);
@@ -223,4 +320,78 @@
 
         return icon;
     }
+
+    /**
+     * Generates a phone number shortcut icon. Adds an overlay describing the type of the phone
+     * number, and if there is a photo also adds the call action icon.
+     */
+    private Bitmap generatePhoneNumberIcon(Bitmap photo, int phoneType, int actionResId) {
+        final Resources r = mContext.getResources();
+        boolean drawPhoneOverlay = true;
+        final float scaleDensity = r.getDisplayMetrics().scaledDensity;
+
+        Bitmap phoneIcon = ((BitmapDrawable) r.getDrawable(actionResId)).getBitmap();
+
+        // If there isn't a photo use the generic phone action icon instead
+        if (photo == null) {
+            photo = phoneIcon;
+            drawPhoneOverlay = false;
+        }
+
+        // Setup the drawing classes
+        Bitmap icon = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(icon);
+
+        // Copy in the photo
+        Paint photoPaint = new Paint();
+        photoPaint.setDither(true);
+        photoPaint.setFilterBitmap(true);
+        Rect src = new Rect(0, 0, photo.getWidth(), photo.getHeight());
+        Rect dst = new Rect(0, 0, mIconSize, mIconSize);
+        canvas.drawBitmap(photo, src, dst, photoPaint);
+
+        // Create an overlay for the phone number type
+        String overlay = null;
+        switch (phoneType) {
+            case Phone.TYPE_HOME:
+                overlay = mContext.getString(R.string.type_short_home);
+                break;
+
+            case Phone.TYPE_MOBILE:
+                overlay = mContext.getString(R.string.type_short_mobile);
+                break;
+
+            case Phone.TYPE_WORK:
+                overlay = mContext.getString(R.string.type_short_work);
+                break;
+
+            case Phone.TYPE_PAGER:
+                overlay = mContext.getString(R.string.type_short_pager);
+                break;
+
+            case Phone.TYPE_OTHER:
+                overlay = mContext.getString(R.string.type_short_other);
+                break;
+        }
+
+        if (overlay != null) {
+            Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
+            textPaint.setTextSize(20.0f * scaleDensity);
+            textPaint.setTypeface(Typeface.DEFAULT_BOLD);
+            textPaint.setColor(r.getColor(R.color.textColorIconOverlay));
+            textPaint.setShadowLayer(3f, 1, 1, r.getColor(R.color.textColorIconOverlayShadow));
+            canvas.drawText(overlay, 2 * scaleDensity, 16 * scaleDensity, textPaint);
+        }
+
+        // Draw the phone action icon as an overlay
+        if (drawPhoneOverlay) {
+            src.set(0, 0, phoneIcon.getWidth(), phoneIcon.getHeight());
+            int iconWidth = icon.getWidth();
+            dst.set(iconWidth - ((int) (20 * scaleDensity)), -1,
+                    iconWidth, ((int) (19 * scaleDensity)));
+            canvas.drawBitmap(phoneIcon, src, dst, photoPaint);
+        }
+
+        return icon;
+    }
 }
diff --git a/src/com/android/contacts/util/Constants.java b/src/com/android/contacts/util/Constants.java
index e0178ad..433d54d 100644
--- a/src/com/android/contacts/util/Constants.java
+++ b/src/com/android/contacts/util/Constants.java
@@ -19,11 +19,6 @@
 import android.app.Service;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 
-/**
- * Background {@link Service} that is used to keep our process alive long enough
- * for background threads to finish. Started and stopped directly by specific
- * background tasks when needed.
- */
 public class Constants {
     /**
      * Specific MIME-type for {@link Phone#CONTENT_ITEM_TYPE} entries that