OSDN Git Service

b/2531257 More work on cleaning up owner account for dupes
[android-x86/packages-apps-Calendar.git] / src / com / android / calendar / EventInfoActivity.java
index 1ca8482..8f4b4cc 100644 (file)
@@ -38,9 +38,7 @@ import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.pim.ContactsAsyncHelper;
 import android.pim.EventRecurrence;
-import android.preference.PreferenceManager;
 import android.provider.Calendar;
 import android.provider.ContactsContract;
 import android.provider.Calendar.Attendees;
@@ -49,9 +47,10 @@ import android.provider.Calendar.Events;
 import android.provider.Calendar.Reminders;
 import android.provider.ContactsContract.CommonDataKinds;
 import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.FastTrack;
+import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.Intents;
 import android.provider.ContactsContract.Presence;
+import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
@@ -69,10 +68,10 @@ import android.view.View;
 import android.view.View.OnTouchListener;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
-import android.widget.FasttrackBadgeWidget;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.QuickContactBadge;
 import android.widget.Spinner;
 import android.widget.TextView;
 import android.widget.Toast;
@@ -112,10 +111,11 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
         Events.HAS_ALARM,            // 10
         Events.ACCESS_LEVEL,         // 11
         Events.COLOR,                // 12
-        Events.GUESTS_CAN_MODIFY,    // 13
+        Events.HAS_ATTENDEE_DATA,    // 13
+        Events.GUESTS_CAN_MODIFY,    // 14
         // TODO Events.GUESTS_CAN_INVITE_OTHERS has not been implemented in calendar provider
-        Events.GUESTS_CAN_INVITE_OTHERS, // 14
-        Events.ORGANIZER,            // 15
+        Events.GUESTS_CAN_INVITE_OTHERS, // 15
+        Events.ORGANIZER,            // 16
     };
     private static final int EVENT_INDEX_ID = 0;
     private static final int EVENT_INDEX_TITLE = 1;
@@ -129,9 +129,10 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
     private static final int EVENT_INDEX_HAS_ALARM = 10;
     private static final int EVENT_INDEX_ACCESS_LEVEL = 11;
     private static final int EVENT_INDEX_COLOR = 12;
-    private static final int EVENT_INDEX_GUESTS_CAN_MODIFY = 13;
-    private static final int EVENT_INDEX_CAN_INVITE_OTHERS = 14;
-    private static final int EVENT_INDEX_ORGANIZER = 15;
+    private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 13;
+    private static final int EVENT_INDEX_GUESTS_CAN_MODIFY = 14;
+    private static final int EVENT_INDEX_CAN_INVITE_OTHERS = 15;
+    private static final int EVENT_INDEX_ORGANIZER = 16;
 
     private static final String[] ATTENDEES_PROJECTION = new String[] {
         Attendees._ID,                      // 0
@@ -155,11 +156,14 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
         Calendars._ID,           // 0
         Calendars.DISPLAY_NAME,  // 1
         Calendars.OWNER_ACCOUNT, // 2
+        Calendars.ORGANIZER_CAN_RESPOND // 3
     };
     static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
     static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
+    static final int CALENDARS_INDEX_OWNER_CAN_RESPOND = 3;
 
     static final String CALENDARS_WHERE = Calendars._ID + "=%d";
+    static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.DISPLAY_NAME + "=?";
 
     private static final String[] REMINDERS_PROJECTION = new String[] {
         Reminders._ID,      // 0
@@ -179,6 +183,7 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
     private static final int MENU_EDIT = 2;
     private static final int MENU_DELETE = 3;
 
+    private static final int ATTENDEE_ID_NONE = -1;
     private static final int ATTENDEE_NO_RESPONSE = -1;
     private static final int[] ATTENDEE_VALUES = {
             ATTENDEE_NO_RESPONSE,
@@ -200,7 +205,10 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
     private long mStartMillis;
     private long mEndMillis;
 
-    private long mCalendarOwnerAttendeeId = -1;
+    private boolean mHasAttendeeData;
+    private boolean mIsOrganizer;
+    private long mCalendarOwnerAttendeeId = ATTENDEE_ID_NONE;
+    private boolean mOrganizerCanRespond;
     private String mCalendarOwnerAccount;
     private boolean mCanModifyCalendar;
     private boolean mIsBusyFreeCalendar;
@@ -213,6 +221,7 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
     private ArrayList<Integer> mReminderValues;
     private ArrayList<String> mReminderLabels;
     private int mDefaultReminderMinutes;
+    private boolean mOriginalHasAlarm;
 
     private DeleteEventHelper mDeleteEventHelper;
     private EditResponseHelper mEditResponseHelper;
@@ -221,6 +230,7 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
     private int mOriginalAttendeeResponse;
     private int mAttendeeResponseFromIntent = ATTENDEE_NO_RESPONSE;
     private boolean mIsRepeating;
+    private boolean mIsDuplicateName;
 
     private Pattern mWildcardPattern = Pattern.compile("^.*$");
     private LayoutInflater mLayoutInflater;
@@ -229,15 +239,14 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
     // TODO This can be removed when the contacts content provider doesn't return duplicates
     private int mUpdateCounts;
     private static class ViewHolder {
-        FasttrackBadgeWidget badge;
+        QuickContactBadge badge;
         ImageView presence;
         int updateCounts;
     }
     private HashMap<String, ViewHolder> mViewHolders = new HashMap<String, ViewHolder>();
     private PresenceQueryHandler mPresenceQueryHandler;
 
-    private static final Uri CONTACT_DATA_WITH_PRESENCE_URI =
-            Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, "data_with_presence");
+    private static final Uri CONTACT_DATA_WITH_PRESENCE_URI = Data.CONTENT_URI;
 
     int PRESENCE_PROJECTION_CONTACT_ID_INDEX = 0;
     int PRESENCE_PROJECTION_PRESENCE_INDEX = 1;
@@ -245,15 +254,16 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
     int PRESENCE_PROJECTION_PHOTO_ID_INDEX = 3;
 
     private static final String[] PRESENCE_PROJECTION = new String[] {
-        Email.CONTACT_ID,          // 0
-        Email.PRESENCE_STATUS,     // 1
-        Email.DATA,                // 2
-        Email.PHOTO_ID,            // 3
+        Email.CONTACT_ID,           // 0
+        Email.CONTACT_PRESENCE,     // 1
+        Email.DATA,                 // 2
+        Email.PHOTO_ID,             // 3
     };
 
     ArrayList<Attendee> mAcceptedAttendees = new ArrayList<Attendee>();
     ArrayList<Attendee> mDeclinedAttendees = new ArrayList<Attendee>();
     ArrayList<Attendee> mTentativeAttendees = new ArrayList<Attendee>();
+    ArrayList<Attendee> mNoResponseAttendees = new ArrayList<Attendee>();
     private int mColor;
 
     // This is called when one of the "remove reminder" buttons is selected.
@@ -304,7 +314,7 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
         mStartMillis = intent.getLongExtra(EVENT_BEGIN_TIME, 0);
         mEndMillis = intent.getLongExtra(EVENT_END_TIME, 0);
         mAttendeeResponseFromIntent = intent.getIntExtra(ATTENDEE_STATUS, ATTENDEE_NO_RESPONSE);
-        mEventCursor = managedQuery(mUri, EVENT_PROJECTION, null, null);
+        mEventCursor = managedQuery(mUri, EVENT_PROJECTION, null, null, null);
         if (initEventCursor()) {
             // The cursor is empty. This can happen if the event was deleted.
             finish();
@@ -321,30 +331,37 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
         // Calendars cursor
         Uri uri = Calendars.CONTENT_URI;
         String where = String.format(CALENDARS_WHERE, mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID));
-        mCalendarsCursor = managedQuery(uri, CALENDARS_PROJECTION, where, null);
+        mCalendarsCursor = managedQuery(uri, CALENDARS_PROJECTION, where, null, null);
         mCalendarOwnerAccount = "";
         if (mCalendarsCursor != null) {
             mCalendarsCursor.moveToFirst();
             mCalendarOwnerAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
+            mOrganizerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0;
+
+            String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
+            mIsDuplicateName = isDuplicateName(displayName);
         }
+        String eventOrganizer = mEventCursor.getString(EVENT_INDEX_ORGANIZER);
+        mIsOrganizer = mCalendarOwnerAccount.equalsIgnoreCase(eventOrganizer);
+        mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
 
         updateView();
 
         // Attendees cursor
         uri = Attendees.CONTENT_URI;
         where = String.format(ATTENDEES_WHERE, mEventId);
-        mAttendeesCursor = managedQuery(uri, ATTENDEES_PROJECTION, where, ATTENDEES_SORT_ORDER);
+        mAttendeesCursor = managedQuery(uri, ATTENDEES_PROJECTION, where, null,
+                ATTENDEES_SORT_ORDER);
         initAttendeesCursor();
 
-        String eventOrganizer = mEventCursor.getString(EVENT_INDEX_ORGANIZER);
         mOrganizer = eventOrganizer;
         mCanModifyCalendar =
                 mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) >= Calendars.CONTRIBUTOR_ACCESS;
         mIsBusyFreeCalendar =
                 mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.FREEBUSY_ACCESS;
+
         mCanModifyEvent = mCanModifyCalendar
-                && (mCalendarOwnerAccount.equals(eventOrganizer)
-                        || mEventCursor.getInt(EVENT_INDEX_GUESTS_CAN_MODIFY) != 0);
+                && (mIsOrganizer || (mEventCursor.getInt(EVENT_INDEX_GUESTS_CAN_MODIFY) != 0));
 
         // Initialize the reminder values array.
         Resources r = getResources();
@@ -358,7 +375,7 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
         String[] labels = r.getStringArray(R.array.reminder_minutes_labels);
         mReminderLabels = new ArrayList<String>(Arrays.asList(labels));
 
-        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(this);
         String durationString =
                 prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0");
         mDefaultReminderMinutes = Integer.parseInt(durationString);
@@ -390,6 +407,7 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
                 reminderCursor.close();
             }
         }
+        mOriginalHasAlarm = hasAlarm;
 
         // Setup the + Add Reminder Button
         View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
@@ -422,13 +440,26 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
 
     private void updateTitle() {
         Resources res = getResources();
-        if (mCanModifyCalendar && mNumOfAttendees > 1) {
+        if (mCanModifyCalendar && !mIsOrganizer) {
             setTitle(res.getString(R.string.event_info_title_invite));
         } else {
             setTitle(res.getString(R.string.event_info_title));
         }
     }
 
+    boolean isDuplicateName(String displayName) {
+        Cursor dupNameCursor = managedQuery(Calendars.CONTENT_URI, CALENDARS_PROJECTION,
+                CALENDARS_DUPLICATE_NAME_WHERE, new String[] {displayName}, null);
+        boolean isDuplicateName = false;
+        if(dupNameCursor != null) {
+            if (dupNameCursor.getCount() > 1) {
+                isDuplicateName = true;
+            }
+            dupNameCursor.close();
+        }
+        return isDuplicateName;
+    }
+
     /**
      * Initializes the event cursor, which is expected to point to the first
      * (and only) result from a query.
@@ -455,9 +486,10 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
         }
     }
 
+    @SuppressWarnings("fallthrough")
     private void initAttendeesCursor() {
         mOriginalAttendeeResponse = ATTENDEE_NO_RESPONSE;
-        mCalendarOwnerAttendeeId = -1;
+        mCalendarOwnerAttendeeId = ATTENDEE_ID_NONE;
         mNumOfAttendees = 0;
         if (mAttendeesCursor != null) {
             mNumOfAttendees = mAttendeesCursor.getCount();
@@ -465,6 +497,7 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
                 mAcceptedAttendees.clear();
                 mDeclinedAttendees.clear();
                 mTentativeAttendees.clear();
+                mNoResponseAttendees.clear();
 
                 do {
                     int status = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
@@ -481,7 +514,8 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
                         }
                     }
 
-                    if (mCalendarOwnerAttendeeId == -1 && mCalendarOwnerAccount.equals(email)) {
+                    if (mCalendarOwnerAttendeeId == ATTENDEE_ID_NONE &&
+                            mCalendarOwnerAccount.equalsIgnoreCase(email)) {
                         mCalendarOwnerAttendeeId = mAttendeesCursor.getInt(ATTENDEES_INDEX_ID);
                         mOriginalAttendeeResponse = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
                     } else {
@@ -495,6 +529,9 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
                             case Attendees.ATTENDEE_STATUS_DECLINED:
                                 mDeclinedAttendees.add(new Attendee(name, email));
                                 break;
+                            case Attendees.ATTENDEE_STATUS_NONE:
+                                mNoResponseAttendees.add(new Attendee(name, email));
+                                // Fallthrough so that no response is a subset of tentative
                             default:
                                 mTentativeAttendees.add(new Attendee(name, email));
                         }
@@ -505,7 +542,10 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
                 updateAttendees();
             }
         }
-        if (mNumOfAttendees > 1) {
+        // only show the organizer if we're not the organizer and if
+        // we have attendee data (might have been removed by the server
+        // for events with a lot of attendees).
+        if (!mIsOrganizer && mHasAttendeeData) {
             mOrganizerContainer.setVisibility(View.VISIBLE);
             mOrganizerView.setText(mOrganizer);
         } else {
@@ -532,13 +572,21 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
         boolean changed = EditEvent.saveReminders(ops, mEventId, reminderMinutes, mOriginalMinutes,
                 false /* no force save */);
         try {
+            // TODO Run this in a background process.
             cr.applyBatch(Calendars.CONTENT_URI.getAuthority(), ops);
+            // Update the "hasAlarm" field for the event
+            Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
+            int len = reminderMinutes.size();
+            boolean hasAlarm = len > 0;
+            if (hasAlarm != mOriginalHasAlarm) {
+                ContentValues values = new ContentValues();
+                values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0);
+                cr.update(uri, values, null, null);
+            }
         } catch (RemoteException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
+            Log.w(TAG, "Ignoring exception: ", e);
         } catch (OperationApplicationException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
+            Log.w(TAG, "Ignoring exception: ", e);
         }
 
         changed |= saveResponse(cr);
@@ -655,6 +703,11 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
             return false;
         }
 
+        // If we never got an owner attendee id we can't set the status
+        if (mCalendarOwnerAttendeeId == ATTENDEE_ID_NONE) {
+            return false;
+        }
+
         if (!mIsRepeating) {
             // This is a non-repeating event
             updateResponse(cr, mEventId, mCalendarOwnerAttendeeId, status);
@@ -680,10 +733,13 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
     }
 
     private void updateResponse(ContentResolver cr, long eventId, long attendeeId, int status) {
-        // Update the "selfAttendeeStatus" field for the event
+        // Update the attendee status in the attendees table.  the provider
+        // takes care of updating the self attendance status.
         ContentValues values = new ContentValues();
 
-        // Will need to add email when MULTIPLE_ATTENDEES_PER_EVENT supported.
+        if (!TextUtils.isEmpty(mCalendarOwnerAccount)) {
+            values.put(Attendees.ATTENDEE_EMAIL, mCalendarOwnerAccount);
+        }
         values.put(Attendees.ATTENDEE_STATUS, status);
         values.put(Attendees.EVENT_ID, eventId);
 
@@ -699,9 +755,12 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
         if (cursor == null) {
             return;
         }
+        if(!cursor.moveToFirst()) {
+            cursor.close();
+            return;
+        }
 
         try {
-            cursor.moveToFirst();
             ContentValues values = new ContentValues();
 
             String title = cursor.getString(EVENT_INDEX_TITLE);
@@ -830,7 +889,8 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
             }
             date.set(mStartMillis);
             eventRecurrence.setStartDate(date);
-            String repeatString = eventRecurrence.getRepeatString();
+            String repeatString = EventRecurrenceFormatter.getRepeatString(getResources(),
+                    eventRecurrence);
             setTextCommon(R.id.repeat, repeatString);
         } else {
             setVisibilityCommon(R.id.repeat_container, View.GONE);
@@ -868,6 +928,15 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
         // Calendar
         if (mCalendarsCursor != null) {
             String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
+            String ownerAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
+            if (mIsDuplicateName && !calendarName.equalsIgnoreCase(ownerAccount)) {
+                Resources res = getResources();
+                TextView ownerText = (TextView) findViewById(R.id.owner);
+                ownerText.setText(ownerAccount);
+                ownerText.setTextColor(res.getColor(R.color.calendar_owner_text_color));
+            } else {
+                setVisibilityCommon(R.id.owner, View.GONE);
+            }
             setTextCommon(R.id.calendar, calendarName);
         } else {
             setVisibilityCommon(R.id.calendar_container, View.GONE);
@@ -875,14 +944,22 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
     }
 
     private void updateAttendees() {
-        CharSequence[] entries;
-        entries = getResources().getTextArray(R.array.response_labels2);
         LinearLayout attendeesLayout = (LinearLayout) findViewById(R.id.attendee_list);
         attendeesLayout.removeAllViewsInLayout();
         ++mUpdateCounts;
-        addAttendeesToLayout(mAcceptedAttendees, attendeesLayout, entries[0]);
-        addAttendeesToLayout(mDeclinedAttendees, attendeesLayout, entries[2]);
-        addAttendeesToLayout(mTentativeAttendees, attendeesLayout, entries[1]);
+        if(mAcceptedAttendees.size() == 0 && mDeclinedAttendees.size() == 0 &&
+                mTentativeAttendees.size() == mNoResponseAttendees.size()) {
+            // If all guests have no response just list them as guests,
+            CharSequence guestsLabel = getResources().getText(R.string.attendees_label);
+            addAttendeesToLayout(mNoResponseAttendees, attendeesLayout, guestsLabel);
+        } else {
+            // If we have any responses then divide them up by response
+            CharSequence[] entries;
+            entries = getResources().getTextArray(R.array.response_labels2);
+            addAttendeesToLayout(mAcceptedAttendees, attendeesLayout, entries[0]);
+            addAttendeesToLayout(mDeclinedAttendees, attendeesLayout, entries[2]);
+            addAttendeesToLayout(mTentativeAttendees, attendeesLayout, entries[1]);
+        }
     }
 
     private void addAttendeesToLayout(ArrayList<Attendee> attendees, LinearLayout attendeeList,
@@ -927,7 +1004,7 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
             tv.setText(name);
 
             ViewHolder vh = new ViewHolder();
-            vh.badge = (FasttrackBadgeWidget) v.findViewById(R.id.badge);
+            vh.badge = (QuickContactBadge) v.findViewById(R.id.badge);
             vh.badge.assignContactFromEmail(attendee.mEmail, true);
             vh.presence = (ImageView) v.findViewById(R.id.presence);
             mViewHolders.put(attendee.mEmail, vh);
@@ -989,6 +1066,8 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
                     if (photoId > 0 && vh.updateCounts < queryIndex) {
                         vh.updateCounts = queryIndex;
                         Uri personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+
+                        // TODO, modify to batch queries together
                         ContactsAsyncHelper.updateImageViewWithContactPhotoAsync(mContext, vh.badge,
                                 personUri, R.drawable.ic_contact_picture);
                     }
@@ -1000,7 +1079,18 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
     }
 
     void updateResponse() {
-        if (!mCanModifyCalendar || mNumOfAttendees <= 1) {
+        // we only let the user accept/reject/etc. a meeting if:
+        // a) you can edit the event's containing calendar AND
+        // b) you're not the organizer and only attendee AND
+        // c) organizerCanRespond is enabled for the calendar
+        // (if the attendee data has been hidden, the visible number of attendees
+        // will be 1 -- the calendar owner's).
+        // (there are more cases involved to be 100% accurate, such as
+        // paying attention to whether or not an attendee status was
+        // included in the feed, but we're currently omitting those corner cases
+        // for simplicity).
+        if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1) ||
+                (mIsOrganizer && !mOrganizerCanRespond)) {
             setVisibilityCommon(R.id.response_container, View.GONE);
             return;
         }
@@ -1068,8 +1158,8 @@ public class EventInfoActivity extends Activity implements View.OnClickListener,
         final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri);
 
         if (lookupUri != null) {
-            // Found matching contact, trigger FastTrack
-            FastTrack.showFastTrack(this, rect, lookupUri, FastTrack.MODE_MEDIUM, null);
+            // Found matching contact, trigger QuickContact
+            QuickContact.showQuickContact(this, rect, lookupUri, QuickContact.MODE_MEDIUM, null);
         } else {
             // No matching contact, ask user to create one
             final Uri mailUri = Uri.fromParts("mailto", address, null);