Adopted SocialContract and first pass at fast-track.

Showing live data from SocialProvider through recently added
SocialContract constants, symlinked for now since the contract
isn't in the framework yet.

Added first pass at fast-track using edge-based triggering
from the social list.  Wraps the ListView in a EdgeTriggerView
that watches for "pull" actions from a specific edge.  Also adds
concept of a FloatyListView to keep a "floaty" window anchored
with respect to ListView scrolling.

The fast-track window summarizes contact methods based on
anyone system-wide who offers an icon for the mime-types.  For
example, the testing app pushes app-specific contact methods
into the Data table, and then provides icons through its
RemoteViewsMapping XML resource.

Changed SHOW_OR_CREATE to accept Aggregate Uris and now shows
fast-track in cases where a single matching aggregate is found.

Abstracted AsyncQueryHandler to a QueryCompletedListener callback
interface to clean up code that uses it while still protecting
against leaked Contexts.
diff --git a/src/com/android/contacts/ShowOrCreateActivity.java b/src/com/android/contacts/ShowOrCreateActivity.java
index 75af4ae..56aef26 100755
--- a/src/com/android/contacts/ShowOrCreateActivity.java
+++ b/src/com/android/contacts/ShowOrCreateActivity.java
@@ -16,16 +16,22 @@
 
 package com.android.contacts;
 
+import com.android.contacts.NotifyingAsyncQueryHandler.QueryCompleteListener;
+import com.android.contacts.SocialStreamActivity.MappingCache;
+import com.android.providers.contacts2.ContactsContract;
+import com.android.providers.contacts2.ContactsContract.Aggregates;
+
 import android.app.Activity;
 import android.app.AlertDialog;
-import android.content.AsyncQueryHandler;
 import android.content.ComponentName;
 import android.content.ContentUris;
+import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.provider.Contacts;
 import android.provider.Contacts.ContactMethods;
 import android.provider.Contacts.ContactMethodsColumns;
@@ -33,6 +39,7 @@
 import android.provider.Contacts.People;
 import android.provider.Contacts.Phones;
 import android.util.Log;
+import android.view.View;
 
 import java.lang.ref.WeakReference;
 
@@ -52,7 +59,7 @@
  * {@link Intent#ACTION_SEARCH}.
  * </ul>
  */
-public final class ShowOrCreateActivity extends Activity {
+public final class ShowOrCreateActivity extends Activity implements QueryCompleteListener {
     static final String TAG = "ShowOrCreateActivity";
     static final boolean LOGD = false;
 
@@ -60,14 +67,15 @@
         Phones.PERSON_ID,
     };
 
-    static final String[] PEOPLE_PROJECTION = new String[] {
-        People._ID,
+    static final String[] CONTACTS_PROJECTION = new String[] {
+        ContactsContract.Contacts.AGGREGATE_ID,
+//        People._ID,
     };
     
     static final String SCHEME_MAILTO = "mailto";
     static final String SCHEME_TEL = "tel";
     
-    static final int PERSON_ID_INDEX = 0;
+    static final int AGGREGATE_ID_INDEX = 0;
 
     /**
      * Query clause to filter {@link ContactMethods#CONTENT_URI} to only search
@@ -76,24 +84,39 @@
     static final String QUERY_KIND_EMAIL_OR_IM = ContactMethodsColumns.KIND +
             " IN (" + Contacts.KIND_EMAIL + "," + Contacts.KIND_IM + ")";
     
+    /**
+     * Extra used to request a specific {@link FastTrackWindow} position.
+     */
+    private static final String EXTRA_Y = "pixel_y";
+    private static final int DEFAULT_Y = 90;
+
     static final int QUERY_TOKEN = 42;
     
-    private QueryHandler mQueryHandler;
+    private NotifyingAsyncQueryHandler mQueryHandler;
+
+    private Bundle mCreateExtras;
+    private String mCreateDescrip;
+    private boolean mCreateForce;
+
+    private FastTrackWindow mFastTrack;
     
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        
+
+        // Throw an empty layout up so we have a window token later
+        setContentView(R.layout.empty);
+
         // Create handler if doesn't exist, otherwise cancel any running
         if (mQueryHandler == null) {
-            mQueryHandler = new QueryHandler(this);
+            mQueryHandler = new NotifyingAsyncQueryHandler(this, this);
         } else {
             mQueryHandler.cancelOperation(QUERY_TOKEN);
         }
 
         final Intent intent = getIntent();
         final Uri data = intent.getData();
-        
+
         // Unpack scheme and target data from intent
         String scheme = null;
         String ssp = null;
@@ -101,52 +124,143 @@
             scheme = data.getScheme();
             ssp = data.getSchemeSpecificPart();
         }
-        
+
         // Build set of extras for possible use when creating contact
-        Bundle createExtras = new Bundle();
+        mCreateExtras = new Bundle();
         Bundle originalExtras = intent.getExtras();
         if (originalExtras != null) {
-            createExtras.putAll(originalExtras);
+            mCreateExtras.putAll(originalExtras);
         }
-        mQueryHandler.setCreateExtras(createExtras);
-        
+
         // Read possible extra with specific title
-        String createDescrip = intent.getStringExtra(Intents.EXTRA_CREATE_DESCRIPTION);
-        if (createDescrip == null) {
-            createDescrip = ssp;
+        String mCreateDescrip = intent.getStringExtra(Intents.EXTRA_CREATE_DESCRIPTION);
+        if (mCreateDescrip == null) {
+            mCreateDescrip = ssp;
         }
-        mQueryHandler.setCreateDescription(createDescrip);
-        
+
         // Allow caller to bypass dialog prompt
-        boolean createForce = intent.getBooleanExtra(Intents.EXTRA_FORCE_CREATE, false);
-        mQueryHandler.setCreateForce(createForce);
-        
+        mCreateForce = intent.getBooleanExtra(Intents.EXTRA_FORCE_CREATE, false);
+
         // Handle specific query request
         if (SCHEME_MAILTO.equals(scheme)) {
-            createExtras.putString(Intents.Insert.EMAIL, ssp);
-            Uri uri = Uri.withAppendedPath(People.WITH_EMAIL_OR_IM_FILTER_URI, Uri.encode(ssp));
+            mCreateExtras.putString(Intents.Insert.EMAIL, ssp);
+//            Uri uri = Uri.withAppendedPath(People.WITH_EMAIL_OR_IM_FILTER_URI, Uri.encode(ssp));
+//            mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
+//                    PEOPLE_PROJECTION, null, null, null);
+
+            Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_EMAIL_FILTER_URI, Uri.encode(ssp));
             mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
-                    PEOPLE_PROJECTION, null, null, null);
+                    CONTACTS_PROJECTION, null, null, null);
+
         } else if (SCHEME_TEL.equals(scheme)) {
-            createExtras.putString(Intents.Insert.PHONE, ssp);
-            mQueryHandler.startQuery(QUERY_TOKEN, null,
-                    Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, ssp),
-                    PHONES_PROJECTION, null, null, null);
-            
+            mCreateExtras.putString(Intents.Insert.PHONE, ssp);
+//            mQueryHandler.startQuery(QUERY_TOKEN, null,
+//                    Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, ssp),
+//                    PHONES_PROJECTION, null, null, null);
+
         } else {
-            Log.w(TAG, "Invalid intent:" + getIntent());
-            finish();
+            // Otherwise assume incoming aggregate Uri
+            final int y = getIntent().getExtras().getInt(EXTRA_Y, DEFAULT_Y);
+            showFastTrack(data, y);
+
         }
     }
-    
+
     @Override
     protected void onStop() {
         super.onStop();
         if (mQueryHandler != null) {
             mQueryHandler.cancelOperation(QUERY_TOKEN);
         }
+        if (mFastTrack != null) {
+            mFastTrack.dismiss();
+        }
     }
-    
+
+    /**
+     * Show a {@link FastTrackWindow} for the given aggregate at the requested
+     * screen location.
+     */
+    private void showFastTrack(Uri aggUri, int y) {
+        // Use our local window token for now
+        final IBinder windowToken = findViewById(android.R.id.empty).getWindowToken();
+        FakeView fakeView = new FakeView(this, windowToken);
+
+        final MappingCache mappingCache = MappingCache.createAndFill(this);
+
+        mFastTrack = new FastTrackWindow(this, fakeView, aggUri, mappingCache);
+        mFastTrack.showAt(0, y);
+    }
+
+    public void onQueryComplete(int token, Object cookie, Cursor cursor) {
+        if (cursor == null) {
+            return;
+        }
+
+        // Count contacts found by query
+        int count = 0;
+        long aggId = -1;
+        try {
+            count = cursor.getCount();
+            if (count == 1 && cursor.moveToFirst()) {
+                // Try reading ID if only one contact returned
+                aggId = cursor.getLong(AGGREGATE_ID_INDEX);
+            }
+        } finally {
+            cursor.close();
+        }
+
+        if (count == 1 && aggId != -1) {
+            // If we only found one item, jump right to viewing it
+            final Uri aggUri = ContentUris.withAppendedId(Aggregates.CONTENT_URI, aggId);
+            final int y = getIntent().getExtras().getInt(EXTRA_Y, DEFAULT_Y);
+            showFastTrack(aggUri, y);
+
+//            Intent viewIntent = new Intent(Intent.ACTION_VIEW,
+//                    ContentUris.withAppendedId(People.CONTENT_URI, personId));
+//            activity.startActivity(viewIntent);
+//            activity.finish();
+
+        } else if (count > 1) {
+            // If more than one, show pick list
+            Intent listIntent = new Intent(Intent.ACTION_SEARCH);
+            listIntent.setComponent(new ComponentName(this, ContactsListActivity.class));
+            listIntent.putExtras(mCreateExtras);
+            startActivity(listIntent);
+            finish();
+
+        } else {
+            // No matching contacts found
+            if (mCreateForce) {
+                // Forced to create new contact
+                Intent createIntent = new Intent(Intent.ACTION_INSERT, People.CONTENT_URI);
+                createIntent.putExtras(mCreateExtras);
+                createIntent.setType(People.CONTENT_TYPE);
+
+                startActivity(createIntent);
+                finish();
+
+            } else {
+                // Prompt user to insert or edit contact
+                Intent createIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+                createIntent.putExtras(mCreateExtras);
+                createIntent.setType(People.CONTENT_ITEM_TYPE);
+
+                CharSequence message = getResources().getString(
+                        R.string.add_contact_dlg_message_fmt, mCreateDescrip);
+
+                new AlertDialog.Builder(this)
+                        .setTitle(R.string.add_contact_dlg_title)
+                        .setMessage(message)
+                        .setPositiveButton(android.R.string.ok,
+                                new IntentClickListener(this, createIntent))
+                        .setNegativeButton(android.R.string.cancel,
+                                new IntentClickListener(this, null))
+                        .show();
+            }
+        }
+    }
+
     /**
      * Listener for {@link DialogInterface} that launches a given {@link Intent}
      * when clicked. When clicked, this also closes the parent using
@@ -174,101 +288,20 @@
     }
 
     /**
-     * Handle asynchronous query to find matching contacts. When query finishes,
-     * will handle based on number of matching contacts found.
+     * Fake view that simply exists to pass through a specific {@link IBinder}
+     * window token.
      */
-    private static final class QueryHandler extends AsyncQueryHandler {
-        private final WeakReference<Activity> mActivity;
-        private Bundle mCreateExtras;
-        private String mCreateDescrip;
-        private boolean mCreateForce;
+    private static class FakeView extends View {
+        private IBinder mWindowToken;
 
-        public QueryHandler(Activity activity) {
-            super(activity.getContentResolver());
-            mActivity = new WeakReference<Activity>(activity);
-        }
-        
-        public void setCreateExtras(Bundle createExtras) {
-            mCreateExtras = createExtras;
-        }
-        
-        public void setCreateDescription(String createDescrip) {
-            mCreateDescrip = createDescrip;
-        }
-        
-        public void setCreateForce(boolean createForce) {
-            mCreateForce = createForce;
+        public FakeView(Context context, IBinder windowToken) {
+            super(context);
+            mWindowToken = windowToken;
         }
 
         @Override
-        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            Activity activity = mActivity.get();
-            if (activity == null) {
-                return;
-            }
-            
-            // Count contacts found by query
-            int count = 0;
-            long personId = -1;
-            if (cursor != null) {
-                try {
-                    count = cursor.getCount();
-                    if (count == 1 && cursor.moveToFirst()) {
-                        // Try reading ID if only one contact returned
-                        personId = cursor.getLong(PERSON_ID_INDEX);
-                    }
-                } finally {
-                    cursor.close();
-                }
-            }
-            
-            if (LOGD) Log.d(TAG, "onQueryComplete count=" + count);
-            
-            if (count == 1) {
-                // If we only found one item, jump right to viewing it
-                Intent viewIntent = new Intent(Intent.ACTION_VIEW,
-                        ContentUris.withAppendedId(People.CONTENT_URI, personId));
-                activity.startActivity(viewIntent);
-                activity.finish();
-                
-            } else if (count > 1) {
-                // If more than one, show pick list
-                Intent listIntent = new Intent(Intent.ACTION_SEARCH);
-                listIntent.setComponent(new ComponentName(activity, ContactsListActivity.class));
-                listIntent.putExtras(mCreateExtras);
-                activity.startActivity(listIntent);
-                activity.finish();
-                
-            } else {
-                // No matching contacts found
-                if (mCreateForce) {
-                    // Forced to create new contact
-                    Intent createIntent = new Intent(Intent.ACTION_INSERT, People.CONTENT_URI);
-                    createIntent.putExtras(mCreateExtras);
-                    createIntent.setType(People.CONTENT_TYPE);
-                    
-                    activity.startActivity(createIntent);
-                    activity.finish();
-                    
-                } else {
-                    // Prompt user to insert or edit contact 
-                    Intent createIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
-                    createIntent.putExtras(mCreateExtras);
-                    createIntent.setType(People.CONTENT_ITEM_TYPE);
-                    
-                    CharSequence message = activity.getResources().getString(
-                            R.string.add_contact_dlg_message_fmt, mCreateDescrip);
-                    
-                    new AlertDialog.Builder(activity)
-                            .setTitle(R.string.add_contact_dlg_title)
-                            .setMessage(message)
-                            .setPositiveButton(android.R.string.ok,
-                                    new IntentClickListener(activity, createIntent))
-                            .setNegativeButton(android.R.string.cancel,
-                                    new IntentClickListener(activity, null))
-                            .show();
-                }
-            }
+        public IBinder getWindowToken() {
+            return mWindowToken;
         }
     }
 }