Iteration on fast-track to show better data and disambig.

Finished up code that allows for disambiguation between
multiple contact methods under same mime-type.  Per design,
the fast-track window expands to show all choices when there
are multiple available.  For example, it shows a list
between "Home", "Work" and "Mobile" if an aggregate has
three phone numbers.  Back key will first dismiss the
disambig list if shown, before dismissing the dialog.

Moved fast-track to use Rect target areas to be more
flexible, instead of splitting up into multiple variables.

Correctly measure the fast-track window now, instead of
relying on hard-coded constants.  Now using the new query
path provided by SocialContract to provide single-line
social summary for a given aggregate, instead of using
hard-coded social string.

Added a third data query to pull display name for aggregate
along with most-present presence from provider.  Changed
fast-track to make chicklets focusable for dpad navigation.
diff --git a/src/com/android/contacts/FastTrackWindow.java b/src/com/android/contacts/FastTrackWindow.java
index 2eae8aa..63ba476 100644
--- a/src/com/android/contacts/FastTrackWindow.java
+++ b/src/com/android/contacts/FastTrackWindow.java
@@ -17,11 +17,10 @@
 package com.android.contacts;
 
 import com.android.contacts.NotifyingAsyncQueryHandler.QueryCompleteListener;
-import com.android.contacts.SocialStreamActivity.MappingCache;
 import com.android.contacts.SocialStreamActivity.Mapping;
+import com.android.contacts.SocialStreamActivity.MappingCache;
 import com.android.internal.policy.PolicyManager;
 
-import android.app.Activity;
 import android.content.ActivityNotFoundException;
 import android.content.ContentUris;
 import android.content.Context;
@@ -31,19 +30,22 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
-import android.graphics.drawable.BitmapDrawable;
+import android.graphics.Rect;
 import android.net.Uri;
-import android.provider.Contacts.Phones;
 import android.provider.ContactsContract;
+import android.provider.SocialContract;
+import android.provider.Contacts.Phones;
 import android.provider.ContactsContract.Aggregates;
 import android.provider.ContactsContract.CommonDataKinds;
 import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Presence;
 import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Im;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.Postal;
+import android.provider.Im.PresenceColumns;
+import android.provider.SocialContract.Activities;
 import android.text.SpannableStringBuilder;
+import android.text.format.DateUtils;
 import android.text.style.CharacterStyle;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.StyleSpan;
@@ -56,42 +58,33 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.View.OnClickListener;
-import android.view.ViewTreeObserver.OnScrollChangedListener;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
+import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ListView;
-import android.widget.PopupWindow;
+import android.widget.RemoteViews;
 import android.widget.TextView;
 import android.widget.Toast;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.Gallery.LayoutParams;
 
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.LinkedList;
-import java.util.PriorityQueue;
 import java.util.Set;
 
 /**
  * Window that shows fast-track contact details for a specific
- * {@link Aggregate#_ID}.
+ * {@link Aggregates#_ID}.
  */
-public class FastTrackWindow implements Window.Callback, QueryCompleteListener, OnClickListener, AbsListView.OnItemClickListener {
+public class FastTrackWindow implements Window.Callback, QueryCompleteListener, OnClickListener,
+        AbsListView.OnItemClickListener {
     private static final String TAG = "FastTrackWindow";
 
     /**
@@ -118,11 +111,10 @@
     private OnDismissListener mDismissListener;
 
     private long mAggId;
-    private int mAnchorX;
-    private int mAnchorY;
-    private int mAnchorHeight;
+    private Rect mAnchor;
 
-    private boolean mHasProfile = false;
+    private boolean mHasSummary = false;
+    private boolean mHasSocial = false;
     private boolean mHasActions = false;
 
     private View mArrowUp;
@@ -132,19 +124,23 @@
     private ImageView mPresence;
     private TextView mContent;
     private TextView mPublished;
+    private HorizontalScrollView mTrackScroll;
     private ViewGroup mTrack;
     private ListView mResolveList;
 
-    // TODO: read from a resource somewhere
-    private static final int mHeight = 138;
-    private static final int mArrowHeight = 10;
-    private static final int mArrowWidth = 24;
+    private String mDisplayName = null;
+    private String mSocialTitle = null;
+
+    private SpannableStringBuilder mBuilder = new SpannableStringBuilder();
+    private CharacterStyle mStyleBold = new StyleSpan(android.graphics.Typeface.BOLD);
+    private CharacterStyle mStyleBlack = new ForegroundColorSpan(Color.BLACK);
 
     /**
      * Set of {@link ActionInfo} that are associated with the aggregate
-     * currently displayed by this fast-track window.
+     * currently displayed by this fast-track window, represented as a map from
+     * {@link String} mimetype to {@link ActionList}.
      */
-    private ActionSet mActions = new ActionSet();
+    private ActionMap mActions = new ActionMap();
 
     /**
      * Specific mime-type for {@link Phone#CONTENT_ITEM_TYPE} entries that
@@ -157,22 +153,17 @@
      * Other mime-types not appearing in this list follow in alphabetic order.
      */
     private static final String[] ORDERED_MIMETYPES = new String[] {
-        Aggregates.CONTENT_ITEM_TYPE,
         Phones.CONTENT_ITEM_TYPE,
+        Aggregates.CONTENT_ITEM_TYPE,
         MIME_SMS_ADDRESS,
         Email.CONTENT_ITEM_TYPE,
     };
 
-//    public static final int ICON_SIZE = 42;
-//    public static final int ICON_PADDING = 3;
-
-    // TODO: read this status from actual query
-    private static final String STUB_STATUS = "has a really long random status message that would be far too long to read on a single device without the need for tiny reading glasses";
-
     private static final boolean INCLUDE_PROFILE_ACTION = true;
 
-    private static final int TOKEN_DISPLAY_NAME = 1;
-    private static final int TOKEN_DATA = 2;
+    private static final int TOKEN_SUMMARY = 1;
+    private static final int TOKEN_SOCIAL = 2;
+    private static final int TOKEN_DATA = 3;
 
     /** Message to show when no activity is found to perform an action */
     // TODO: move this value into a resources string
@@ -192,14 +183,15 @@
 
         mWindow.setContentView(R.layout.fasttrack);
 
-        mArrowUp = (View)mWindow.findViewById(R.id.arrow_up);
-        mArrowDown = (View)mWindow.findViewById(R.id.arrow_down);
-        
+        mArrowUp = mWindow.findViewById(R.id.arrow_up);
+        mArrowDown = mWindow.findViewById(R.id.arrow_down);
+
         mPhoto = (ImageView)mWindow.findViewById(R.id.photo);
         mPresence = (ImageView)mWindow.findViewById(R.id.presence);
         mContent = (TextView)mWindow.findViewById(R.id.content);
         mPublished = (TextView)mWindow.findViewById(R.id.published);
         mTrack = (ViewGroup)mWindow.findViewById(R.id.fasttrack);
+        mTrackScroll = (HorizontalScrollView)mWindow.findViewById(R.id.scroll);
         mResolveList = (ListView)mWindow.findViewById(android.R.id.list);
 
         // TODO: move generation of mime-type cache to more-efficient place
@@ -233,17 +225,20 @@
         mMappingCache.addMapping(mapping);
 
         mapping = new Mapping(CommonDataKinds.PACKAGE_COMMON, Phone.CONTENT_ITEM_TYPE);
-        mapping.summaryColumn = Phone.NUMBER;
+        mapping.summaryColumn = Phone.TYPE;
+        mapping.detailColumn = Phone.NUMBER;
         mapping.icon = BitmapFactory.decodeResource(res, android.R.drawable.sym_action_call);
         mMappingCache.addMapping(mapping);
 
         mapping = new Mapping(CommonDataKinds.PACKAGE_COMMON, MIME_SMS_ADDRESS);
-        mapping.summaryColumn = Phone.NUMBER;
+        mapping.summaryColumn = Phone.TYPE;
+        mapping.detailColumn = Phone.NUMBER;
         mapping.icon = BitmapFactory.decodeResource(res, R.drawable.sym_action_sms);
         mMappingCache.addMapping(mapping);
 
         mapping = new Mapping(CommonDataKinds.PACKAGE_COMMON, Email.CONTENT_ITEM_TYPE);
-        mapping.summaryColumn = Email.DATA;
+        mapping.summaryColumn = Email.TYPE;
+        mapping.detailColumn = Email.DATA;
         mapping.icon = BitmapFactory.decodeResource(res, android.R.drawable.sym_action_email);
         mMappingCache.addMapping(mapping);
 
@@ -253,26 +248,28 @@
      * Start showing a fast-track window for the given {@link Aggregate#_ID}
      * pointing towards the given location.
      */
-    public void show(Uri aggUri, int x, int y, int height) {
+    public void show(Uri aggUri, Rect anchor) {
         if (mShowing || mQuerying) {
             Log.w(TAG, "already in process of showing");
             return;
         }
 
         mAggId = ContentUris.parseId(aggUri);
-        mAnchorX = x;
-        mAnchorY = y;
-        mAnchorHeight = height;
+        mAnchor = new Rect(anchor);
         mQuerying = true;
 
-        // Start data query in background
-        Uri dataUri = Uri.withAppendedPath(aggUri,
+        Uri aggSummary = ContentUris.withAppendedId(
+                ContactsContract.Aggregates.CONTENT_SUMMARY_URI, mAggId);
+        Uri aggSocial = ContentUris.withAppendedId(
+                SocialContract.Activities.CONTENT_AGGREGATE_STATUS_URI, mAggId);
+        Uri aggData = Uri.withAppendedPath(aggUri,
                 ContactsContract.Aggregates.Data.CONTENT_DIRECTORY);
 
-        // TODO: also query for latest status message
+        // Start data query in background
         mHandler = new NotifyingAsyncQueryHandler(mContext, this);
-        mHandler.startQuery(TOKEN_DISPLAY_NAME, null, aggUri, null, null, null, null);
-        mHandler.startQuery(TOKEN_DATA, null, dataUri, null, null, null, null);
+        mHandler.startQuery(TOKEN_SUMMARY, null, aggSummary, null, null, null, null);
+        mHandler.startQuery(TOKEN_SOCIAL, null, aggSocial, null, null, null, null);
+        mHandler.startQuery(TOKEN_DATA, null, aggData, null, null, null, null);
 
     }
 
@@ -282,11 +279,13 @@
     private void showArrow(int whichArrow, int requestedX) {
         final View showArrow = (whichArrow == R.id.arrow_up) ? mArrowUp : mArrowDown;
         final View hideArrow = (whichArrow == R.id.arrow_up) ? mArrowDown : mArrowUp;
-        
+
+        final int arrowWidth = mArrowUp.getMeasuredWidth();
+
         showArrow.setVisibility(View.VISIBLE);
         LinearLayout.LayoutParams param = (LinearLayout.LayoutParams)showArrow.getLayoutParams();
-        param.leftMargin = requestedX - mArrowWidth / 2;
-        
+        param.leftMargin = requestedX - arrowWidth / 2;
+
         hideArrow.setVisibility(View.INVISIBLE);
     }
 
@@ -297,29 +296,37 @@
     private void showInternal() {
         mDecor = mWindow.getDecorView();
         WindowManager.LayoutParams l = mWindow.getAttributes();
-        
+
+        l.width = WindowManager.LayoutParams.FILL_PARENT;
+        l.height = WindowManager.LayoutParams.WRAP_CONTENT;
+
+        // Force layout measuring pass so we have baseline numbers
+        mDecor.measure(l.width, l.height);
+
+        final int blockHeight = mDecor.getMeasuredHeight();
+        final int arrowHeight = mArrowUp.getHeight();
+
         l.gravity = Gravity.TOP | Gravity.LEFT;
         l.x = 0;
-        
-        if (mAnchorY > mHeight) {
-            // Show downwards callout when enough room
-            showArrow(R.id.arrow_down, mAnchorX);
-            l.y = mAnchorY - (mHeight - (mArrowHeight * 2) - 5);
-            
+
+        if (mAnchor.top > blockHeight) {
+            // Show downwards callout when enough room, aligning bottom block
+            // edge with top of anchor area, and adjusting to inset arrow.
+            showArrow(R.id.arrow_down, mAnchor.centerX());
+            l.y = mAnchor.top - blockHeight + arrowHeight;
+
         } else {
-            // Otherwise show upwards callout
-            showArrow(R.id.arrow_up, mAnchorX);
-            l.y = mAnchorY + mAnchorHeight - 10;
-            
+            // Otherwise show upwards callout, aligning block top with bottom of
+            // anchor area, and adjusting to inset arrow.
+            showArrow(R.id.arrow_up, mAnchor.centerX());
+            l.y = mAnchor.bottom - arrowHeight;
+
         }
-        
-        l.width = WindowManager.LayoutParams.FILL_PARENT;
-        l.height = mHeight;
 
         l.dimAmount = 0.6f;
         l.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND
                 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 
@@ -338,19 +345,28 @@
         }
 
         // Cancel any pending queries
-        mHandler.cancelOperation(TOKEN_DISPLAY_NAME);
+        mHandler.cancelOperation(TOKEN_SUMMARY);
+        mHandler.cancelOperation(TOKEN_SOCIAL);
         mHandler.cancelOperation(TOKEN_DATA);
 
         // Reset all views to prepare for possible recycling
         mPhoto.setImageResource(R.drawable.ic_contact_list_picture);
-//        mPresence.setImageDrawable(null);
-//        mPublished.setText(null);
+        mPresence.setImageDrawable(null);
+        mPublished.setText(null);
         mContent.setText(null);
 
+        mDisplayName = null;
+        mSocialTitle = null;
+
+        // Clear track actions and scroll to hard left
         mActions.clear();
         mTrack.removeAllViews();
+        mTrackScroll.fullScroll(View.FOCUS_LEFT);
+        mWasDownArrow = false;
 
-        mHasProfile = false;
+        showResolveList(View.GONE);
+
+        mHasSocial = false;
         mHasActions = false;
 
         if (mDecor == null || !mShowing) {
@@ -381,7 +397,16 @@
      * {@link #showInternal()} when all data items are present.
      */
     private synchronized void considerShowing() {
-        if (mHasActions && mHasProfile && !mShowing) {
+        if (mHasSummary && mHasSocial && mHasActions && !mShowing) {
+            // Now that all queries have been finished, build summary string.
+            mBuilder.clear();
+            mBuilder.append(mDisplayName);
+            mBuilder.append(" ");
+            mBuilder.append(mSocialTitle);
+            mBuilder.setSpan(mStyleBold, 0, mDisplayName.length(), 0);
+            mBuilder.setSpan(mStyleBlack, 0, mDisplayName.length(), 0);
+            mContent.setText(mBuilder);
+
             showInternal();
         }
     }
@@ -390,39 +415,52 @@
     public void onQueryComplete(int token, Object cookie, Cursor cursor) {
         if (cursor == null) {
             return;
-        } else if (token == TOKEN_DISPLAY_NAME) {
-            handleDisplayName(cursor);
+        } else if (token == TOKEN_SUMMARY) {
+            handleSummary(cursor);
+        } else if (token == TOKEN_SOCIAL) {
+            handleSocial(cursor);
         } else if (token == TOKEN_DATA) {
             handleData(cursor);
         }
     }
-    
-    private SpannableStringBuilder mBuilder = new SpannableStringBuilder();
-    private CharacterStyle mStyleBold = new StyleSpan(android.graphics.Typeface.BOLD);
-    private CharacterStyle mStyleBlack = new ForegroundColorSpan(Color.BLACK);
 
     /**
-     * Handle the result from the {@link TOKEN_DISPLAY_NAME} query.
+     * Handle the result from the {@link TOKEN_SUMMARY} query.
      */
-    private void handleDisplayName(Cursor cursor) {
-        final int COL_DISPLAY_NAME = cursor.getColumnIndex(Aggregates.DISPLAY_NAME);
+    private void handleSummary(Cursor cursor) {
+        final int colDisplayName = cursor.getColumnIndex(Aggregates.DISPLAY_NAME);
+        final int colStatus = cursor.getColumnIndex(PresenceColumns.PRESENCE_STATUS);
 
         if (cursor.moveToNext()) {
-            String foundName = cursor.getString(COL_DISPLAY_NAME);
-            
-            mBuilder.clear();
-            mBuilder.append(foundName);
-            mBuilder.append(" ");
-            mBuilder.append(STUB_STATUS);
-            mBuilder.setSpan(mStyleBold, 0, foundName.length(), 0);
-            mBuilder.setSpan(mStyleBlack, 0, foundName.length(), 0);
-            mContent.setText(mBuilder);
-            
-            mPublished.setText("4 hours ago");
+            mDisplayName = cursor.getString(colDisplayName);
+
+            int status = cursor.getInt(colStatus);
+            int statusRes = Presence.getPresenceIconResourceId(status);
+            mPresence.setImageResource(statusRes);
+        }
+
+        mHasSummary = true;
+        considerShowing();
+    }
+
+    /**
+     * Handle the result from the {@link TOKEN_SOCIAL} query.
+     */
+    private void handleSocial(Cursor cursor) {
+        final int colTitle = cursor.getColumnIndex(Activities.TITLE);
+        final int colPublished = cursor.getColumnIndex(Activities.PUBLISHED);
+
+        if (cursor.moveToNext()) {
+            mSocialTitle = cursor.getString(colTitle);
+
+            long published = cursor.getLong(colPublished);
+            CharSequence relativePublished = DateUtils.getRelativeTimeSpanString(published,
+                    System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS);
+            mPublished.setText(relativePublished);
 
         }
 
-        mHasProfile = true;
+        mHasSocial = true;
         considerShowing();
     }
 
@@ -437,7 +475,8 @@
         String mimeType;
 
         Mapping mapping;
-        String summaryValue;
+        CharSequence summaryValue;
+        String detailValue;
 
         /**
          * Create an action from common {@link Data} elements.
@@ -461,11 +500,19 @@
          * Given a {@link Cursor} pointed at the {@link Data} row associated
          * with this action, use the {@link Mapping} to build a text summary.
          */
-        public void buildSummary(Cursor cursor) {
-            if (mapping == null || mapping.summaryColumn == null) return;
-            int index = cursor.getColumnIndex(mapping.summaryColumn);
-            if (index != -1) {
-                summaryValue = cursor.getString(index);
+        public void buildDetails(Cursor cursor) {
+            if (mapping == null) return;
+
+            // Try finding common display label for this item, otherwise fall
+            // back to reading from defined summary column.
+            summaryValue = ContactsUtils.getDisplayLabel(mContext, mimeType, cursor);
+            if (summaryValue == null && mapping.summaryColumn != null) {
+                summaryValue = cursor.getString(cursor.getColumnIndex(mapping.summaryColumn));
+            }
+
+            // Read detailed value, if mapping was defined
+            if (mapping.detailColumn != null) {
+                detailValue = cursor.getString(cursor.getColumnIndex(mapping.detailColumn));
             }
         }
 
@@ -475,15 +522,15 @@
         public Intent buildIntent() {
             // Handle well-known mime-types with special care
             if (CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                Uri callUri = Uri.parse("tel:" + Uri.encode(summaryValue));
+                Uri callUri = Uri.parse("tel:" + Uri.encode(detailValue));
                 return new Intent(Intent.ACTION_DIAL, callUri);
 
             } else if (MIME_SMS_ADDRESS.equals(mimeType)) {
-                Uri smsUri = Uri.fromParts("smsto", summaryValue, null);
+                Uri smsUri = Uri.fromParts("smsto", detailValue, null);
                 return new Intent(Intent.ACTION_SENDTO, smsUri);
 
             } else if (CommonDataKinds.Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                Uri mailUri = Uri.fromParts("mailto", summaryValue, null);
+                Uri mailUri = Uri.fromParts("mailto", detailValue, null);
                 return new Intent(Intent.ACTION_SENDTO, mailUri);
 
             }
@@ -499,16 +546,24 @@
     }
 
     /**
+     * Provide a strongly-typed {@link LinkedList} that holds a list of
+     * {@link ActionInfo} objects.
+     */
+    private class ActionList extends LinkedList<ActionInfo> {
+    }
+
+    /**
      * Provide a simple way of collecting one or more {@link ActionInfo} objects
      * under a mime-type key.
      */
-    private class ActionSet extends HashMap<String, LinkedList<ActionInfo>> {
+    private class ActionMap extends HashMap<String, ActionList> {
         private void collect(String mimeType, ActionInfo info) {
-            // Create mime-type set if needed
-            if (!containsKey(mimeType)) {
-                put(mimeType, new LinkedList<ActionInfo>());
+            // Create list for this mimetype when needed
+            ActionList collectList = get(mimeType);
+            if (collectList == null) {
+                collectList = new ActionList();
+                put(mimeType, collectList);
             }
-            LinkedList<ActionInfo> collectList = get(mimeType);
             collectList.add(info);
         }
     }
@@ -517,10 +572,10 @@
      * Handle the result from the {@link TOKEN_DATA} query.
      */
     private void handleData(Cursor cursor) {
-        final int COL_ID = cursor.getColumnIndex(Data._ID);
-        final int COL_PACKAGE = cursor.getColumnIndex(Data.PACKAGE);
-        final int COL_MIMETYPE = cursor.getColumnIndex(Data.MIMETYPE);
-        final int COL_PHOTO = cursor.getColumnIndex(Photo.PHOTO);
+        final int colId = cursor.getColumnIndex(Data._ID);
+        final int colPackage = cursor.getColumnIndex(Data.PACKAGE);
+        final int colMimeType = cursor.getColumnIndex(Data.MIMETYPE);
+        final int colPhoto = cursor.getColumnIndex(Photo.PHOTO);
 
         ActionInfo info;
 
@@ -534,14 +589,14 @@
         }
 
         while (cursor.moveToNext()) {
-            final long dataId = cursor.getLong(COL_ID);
-            final String packageName = cursor.getString(COL_PACKAGE);
-            final String mimeType = cursor.getString(COL_MIMETYPE);
+            final long dataId = cursor.getLong(colId);
+            final String packageName = cursor.getString(colPackage);
+            final String mimeType = cursor.getString(colMimeType);
 
             // Handle when a photo appears in the various data items
             // TODO: accept a photo only if its marked as primary
             if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                byte[] photoBlob = cursor.getBlob(COL_PHOTO);
+                byte[] photoBlob = cursor.getBlob(colPhoto);
                 Bitmap photoBitmap = BitmapFactory.decodeByteArray(photoBlob, 0, photoBlob.length);
                 mPhoto.setImageBitmap(photoBitmap);
                 continue;
@@ -552,7 +607,7 @@
             // with all others of this mime-type.
             info = new ActionInfo(dataId, packageName, mimeType);
             if (info.findMapping(mMappingCache)) {
-                info.buildSummary(cursor);
+                info.buildDetails(cursor);
                 mActions.collect(info.mimeType, info);
             }
 
@@ -560,7 +615,7 @@
             if (Phones.CONTENT_ITEM_TYPE.equals(mimeType)) {
                 info = new ActionInfo(dataId, packageName, MIME_SMS_ADDRESS);
                 if (info.findMapping(mMappingCache)) {
-                    info.buildSummary(cursor);
+                    info.buildDetails(cursor);
                     mActions.collect(info.mimeType, info);
                 }
             }
@@ -596,33 +651,61 @@
         ImageView view = (ImageView)mInflater.inflate(R.layout.fasttrack_item, mTrack, false);
 
         // Add direct intent if single child, otherwise flag for multiple
-        LinkedList<ActionInfo> children = mActions.get(mimeType);
+        ActionList children = mActions.get(mimeType);
         ActionInfo firstInfo = children.get(0);
-//        if (children.size() == 1) {
+        if (children.size() == 1) {
             view.setTag(firstInfo.buildIntent());
-//        } else {
-//            view.setTag(children);
-//        }
+        } else {
+            view.setTag(children);
+        }
 
         // Set icon and listen for clicks
         view.setImageBitmap(firstInfo.mapping.icon);
         view.setOnClickListener(this);
         return view;
     }
-    
+
     /** {@inheritDoc} */
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
         // Pass list item clicks along so that Intents are handled uniformly
         onClick(view);
     }
 
+    /**
+     * Flag indicating if {@link #mArrowDown} was visible during the last call
+     * to {@link #showResolveList(int)}. Used to decide during a later call if
+     * the arrow should be restored.
+     */
+    private boolean mWasDownArrow = false;
+
+    /**
+     * Helper for showing and hiding {@link #mResolveList}, which will correctly
+     * manage {@link #mArrowDown} as needed.
+     */
+    private void showResolveList(int visibility) {
+        // Show or hide the resolve list if needed
+        if (mResolveList.getVisibility() != visibility) {
+            mResolveList.setVisibility(visibility);
+        }
+
+        if (visibility == View.VISIBLE) {
+            // If showing list, then hide and save state of down arrow
+            mWasDownArrow = mWasDownArrow || (mArrowDown.getVisibility() == View.VISIBLE);
+            mArrowDown.setVisibility(View.INVISIBLE);
+        } else {
+            // If hiding list, restore any down arrow state
+            mArrowDown.setVisibility(mWasDownArrow ? View.VISIBLE : View.INVISIBLE);
+        }
+
+    }
+
     /** {@inheritDoc} */
     public void onClick(View v) {
         final Object tag = v.getTag();
         if (tag instanceof Intent) {
             // Hide the resolution list, if present
-            mResolveList.setVisibility(View.GONE);
-            
+            showResolveList(View.GONE);
+
             // Incoming tag is concrete intent, so launch
             try {
                 mContext.startActivity((Intent)tag);
@@ -630,54 +713,67 @@
                 Log.w(TAG, NOT_FOUND);
                 Toast.makeText(mContext, NOT_FOUND, Toast.LENGTH_SHORT).show();
             }
-//        } else if (tag instanceof LinkedList) {
-//            // Incoming tag is a mime-type, so show resolution list
-//            final LinkedList<ActionInfo> children = (LinkedList<ActionInfo>)tag;
-//            Log.d(TAG, "found chidlren=" + children);
-//
-//            mResolveList.setVisibility(View.VISIBLE);
-//            mResolveList.setOnItemClickListener(this);
-//            mResolveList.setAdapter(new BaseAdapter() {
-//                public int getCount() {
-//                    return children.size();
-//                }
-//
-//                public Object getItem(int position) {
-//                    return children.get(position);
-//                }
-//
-//                public long getItemId(int position) {
-//                    return position;
-//                }
-//
-//                public View getView(int position, View convertView, ViewGroup parent) {
-//                    if (convertView == null) {
-//                        convertView = mInflater.inflate(R.layout.fasttrack_resolve_item, parent, false);
-//                    }
-//                    
-//                    // Set action title based on summary value
-//                    ActionInfo info = (ActionInfo)getItem(position);
-//                    TextView textView = (TextView)convertView;
-//                    textView.setText(info.summaryValue);
-//                    textView.setTag(info.buildIntent());
-//                    textView.setCompoundDrawablesWithIntrinsicBounds(
-//                            new BitmapDrawable(info.mapping.icon), null, null, null);
-//                    
-//                    return convertView;
-//                }
-//            });
-//            
-//            // Make sure we resize to make room for ListView
-//            onWindowAttributesChanged(mWindow.getAttributes());
-//
-//            // TODO: show drop-down resolution list
-//            Log.d(TAG, "would show list between several options here");
+        } else if (tag instanceof ActionList) {
+            // Incoming tag is a mime-type, so show resolution list
+            final ActionList children = (ActionList)tag;
+
+            // Show resolution list and set adapter
+            showResolveList(View.VISIBLE);
+
+            mResolveList.setOnItemClickListener(this);
+            mResolveList.setAdapter(new BaseAdapter() {
+                public int getCount() {
+                    return children.size();
+                }
+
+                public Object getItem(int position) {
+                    return children.get(position);
+                }
+
+                public long getItemId(int position) {
+                    return position;
+                }
+
+                public View getView(int position, View convertView, ViewGroup parent) {
+                    if (convertView == null) {
+                        convertView = mInflater.inflate(R.layout.fasttrack_resolve_item, parent, false);
+                    }
+
+                    // Set action title based on summary value
+                    ActionInfo info = (ActionInfo)getItem(position);
+
+                    ImageView icon1 = (ImageView)convertView.findViewById(android.R.id.icon1);
+                    TextView text1 = (TextView)convertView.findViewById(android.R.id.text1);
+                    TextView text2 = (TextView)convertView.findViewById(android.R.id.text2);
+
+                    icon1.setImageBitmap(info.mapping.icon);
+                    text1.setText(info.summaryValue);
+                    text2.setText(info.detailValue);
+
+                    convertView.setTag(info.buildIntent());
+                    return convertView;
+                }
+            });
+
+            // Make sure we resize to make room for ListView
+            onWindowAttributesChanged(mWindow.getAttributes());
 
         }
     }
 
     /** {@inheritDoc} */
     public boolean dispatchKeyEvent(KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_DOWN
+                && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+            // Back key will first dismiss any expanded resolve list, otherwise
+            // it will close the entire dialog.
+            if (mResolveList.getVisibility() == View.VISIBLE) {
+                showResolveList(View.GONE);
+            } else {
+                dismiss();
+            }
+            return true;
+        }
         return mWindow.superDispatchKeyEvent(event);
     }