QuickContactBadge.java revision 617feb99a06e7ffb3894e86a286bf30e085f321a
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import com.android.internal.R;
20
21import android.content.AsyncQueryHandler;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.res.TypedArray;
26import android.database.Cursor;
27import android.graphics.Canvas;
28import android.graphics.drawable.Drawable;
29import android.net.Uri;
30import android.os.Bundle;
31import android.provider.ContactsContract.CommonDataKinds.Email;
32import android.provider.ContactsContract.Contacts;
33import android.provider.ContactsContract.Intents;
34import android.provider.ContactsContract.PhoneLookup;
35import android.provider.ContactsContract.QuickContact;
36import android.provider.ContactsContract.RawContacts;
37import android.util.AttributeSet;
38import android.view.View;
39import android.view.View.OnClickListener;
40import android.view.accessibility.AccessibilityEvent;
41import android.view.accessibility.AccessibilityNodeInfo;
42
43/**
44 * Widget used to show an image with the standard QuickContact badge
45 * and on-click behavior.
46 */
47public class QuickContactBadge extends ImageView implements OnClickListener {
48    private Uri mContactUri;
49    private String mContactEmail;
50    private String mContactPhone;
51    private Drawable mOverlay;
52    private QueryHandler mQueryHandler;
53    private Drawable mDefaultAvatar;
54    private Bundle mExtras = null;
55
56    protected String[] mExcludeMimes = null;
57
58    static final private int TOKEN_EMAIL_LOOKUP = 0;
59    static final private int TOKEN_PHONE_LOOKUP = 1;
60    static final private int TOKEN_EMAIL_LOOKUP_AND_TRIGGER = 2;
61    static final private int TOKEN_PHONE_LOOKUP_AND_TRIGGER = 3;
62
63    static final private String EXTRA_URI_CONTENT = "uri_content";
64
65    static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
66        RawContacts.CONTACT_ID,
67        Contacts.LOOKUP_KEY,
68    };
69    static final int EMAIL_ID_COLUMN_INDEX = 0;
70    static final int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1;
71
72    static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
73        PhoneLookup._ID,
74        PhoneLookup.LOOKUP_KEY,
75    };
76    static final int PHONE_ID_COLUMN_INDEX = 0;
77    static final int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1;
78
79    public QuickContactBadge(Context context) {
80        this(context, null);
81    }
82
83    public QuickContactBadge(Context context, AttributeSet attrs) {
84        this(context, attrs, 0);
85    }
86
87    public QuickContactBadge(Context context, AttributeSet attrs, int defStyleAttr) {
88        this(context, attrs, defStyleAttr, 0);
89    }
90
91    public QuickContactBadge(
92            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
93        super(context, attrs, defStyleAttr, defStyleRes);
94
95        TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme);
96        mOverlay = styledAttributes.getDrawable(
97                com.android.internal.R.styleable.Theme_quickContactBadgeOverlay);
98        styledAttributes.recycle();
99
100        mQueryHandler = new QueryHandler(mContext.getContentResolver());
101        setOnClickListener(this);
102    }
103
104    @Override
105    protected void drawableStateChanged() {
106        super.drawableStateChanged();
107        if (mOverlay != null && mOverlay.isStateful()) {
108            mOverlay.setState(getDrawableState());
109            invalidate();
110        }
111    }
112
113    /** This call has no effect anymore, as there is only one QuickContact mode */
114    @SuppressWarnings("unused")
115    public void setMode(int size) {
116    }
117
118    @Override
119    protected void onDraw(Canvas canvas) {
120        super.onDraw(canvas);
121
122        if (!isEnabled()) {
123            // not clickable? don't show triangle
124            return;
125        }
126
127        if (mOverlay == null || mOverlay.getIntrinsicWidth() == 0 ||
128                mOverlay.getIntrinsicHeight() == 0) {
129            // nothing to draw
130            return;
131        }
132
133        mOverlay.setBounds(0, 0, getWidth(), getHeight());
134
135        if (mPaddingTop == 0 && mPaddingLeft == 0) {
136            mOverlay.draw(canvas);
137        } else {
138            int saveCount = canvas.getSaveCount();
139            canvas.save();
140            canvas.translate(mPaddingLeft, mPaddingTop);
141            mOverlay.draw(canvas);
142            canvas.restoreToCount(saveCount);
143        }
144    }
145
146    /** True if a contact, an email address or a phone number has been assigned */
147    private boolean isAssigned() {
148        return mContactUri != null || mContactEmail != null || mContactPhone != null;
149    }
150
151    /**
152     * Resets the contact photo to the default state.
153     */
154    public void setImageToDefault() {
155        if (mDefaultAvatar == null) {
156            mDefaultAvatar = getResources().getDrawable(R.drawable.ic_contact_picture);
157        }
158        setImageDrawable(mDefaultAvatar);
159    }
160
161    /**
162     * Assign the contact uri that this QuickContactBadge should be associated
163     * with. Note that this is only used for displaying the QuickContact window and
164     * won't bind the contact's photo for you. Call {@link #setImageDrawable(Drawable)} to set the
165     * photo.
166     *
167     * @param contactUri Either a {@link Contacts#CONTENT_URI} or
168     *            {@link Contacts#CONTENT_LOOKUP_URI} style URI.
169     */
170    public void assignContactUri(Uri contactUri) {
171        mContactUri = contactUri;
172        mContactEmail = null;
173        mContactPhone = null;
174        onContactUriChanged();
175    }
176
177    /**
178     * Assign a contact based on an email address. This should only be used when
179     * the contact's URI is not available, as an extra query will have to be
180     * performed to lookup the URI based on the email.
181     *
182     * @param emailAddress The email address of the contact.
183     * @param lazyLookup If this is true, the lookup query will not be performed
184     * until this view is clicked.
185     */
186    public void assignContactFromEmail(String emailAddress, boolean lazyLookup) {
187        assignContactFromEmail(emailAddress, lazyLookup, null);
188    }
189
190    /**
191     * Assign a contact based on an email address. This should only be used when
192     * the contact's URI is not available, as an extra query will have to be
193     * performed to lookup the URI based on the email.
194
195     @param emailAddress The email address of the contact.
196     @param lazyLookup If this is true, the lookup query will not be performed
197     until this view is clicked.
198     @param extras A bundle of extras to populate the contact edit page with if the contact
199     is not found and the user chooses to add the email address to an existing contact or
200     create a new contact. Uses the same string constants as those found in
201     {@link android.provider.ContactsContract.Intents.Insert}
202    */
203
204    public void assignContactFromEmail(String emailAddress, boolean lazyLookup, Bundle extras) {
205        mContactEmail = emailAddress;
206        mExtras = extras;
207        if (!lazyLookup) {
208            mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, null,
209                    Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
210                    EMAIL_LOOKUP_PROJECTION, null, null, null);
211        } else {
212            mContactUri = null;
213            onContactUriChanged();
214        }
215    }
216
217
218    /**
219     * Assign a contact based on a phone number. This should only be used when
220     * the contact's URI is not available, as an extra query will have to be
221     * performed to lookup the URI based on the phone number.
222     *
223     * @param phoneNumber The phone number of the contact.
224     * @param lazyLookup If this is true, the lookup query will not be performed
225     * until this view is clicked.
226     */
227    public void assignContactFromPhone(String phoneNumber, boolean lazyLookup) {
228        assignContactFromPhone(phoneNumber, lazyLookup, new Bundle());
229    }
230
231    /**
232     * Assign a contact based on a phone number. This should only be used when
233     * the contact's URI is not available, as an extra query will have to be
234     * performed to lookup the URI based on the phone number.
235     *
236     * @param phoneNumber The phone number of the contact.
237     * @param lazyLookup If this is true, the lookup query will not be performed
238     * until this view is clicked.
239     * @param extras A bundle of extras to populate the contact edit page with if the contact
240     * is not found and the user chooses to add the phone number to an existing contact or
241     * create a new contact. Uses the same string constants as those found in
242     * {@link android.provider.ContactsContract.Intents.Insert}
243     */
244    public void assignContactFromPhone(String phoneNumber, boolean lazyLookup, Bundle extras) {
245        mContactPhone = phoneNumber;
246        mExtras = extras;
247        if (!lazyLookup) {
248            mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, null,
249                    Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone),
250                    PHONE_LOOKUP_PROJECTION, null, null, null);
251        } else {
252            mContactUri = null;
253            onContactUriChanged();
254        }
255    }
256
257    private void onContactUriChanged() {
258        setEnabled(isAssigned());
259    }
260
261    @Override
262    public void onClick(View v) {
263        // If contact has been assigned, mExtras should no longer be null, but do a null check
264        // anyway just in case assignContactFromPhone or Email was called with a null bundle or
265        // wasn't assigned previously.
266        final Bundle extras = (mExtras == null) ? new Bundle() : mExtras;
267        if (mContactUri != null) {
268            QuickContact.showQuickContact(getContext(), QuickContactBadge.this, mContactUri,
269                    QuickContact.MODE_LARGE, mExcludeMimes);
270        } else if (mContactEmail != null) {
271            extras.putString(EXTRA_URI_CONTENT, mContactEmail);
272            mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, extras,
273                    Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
274                    EMAIL_LOOKUP_PROJECTION, null, null, null);
275        } else if (mContactPhone != null) {
276            extras.putString(EXTRA_URI_CONTENT, mContactPhone);
277            mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP_AND_TRIGGER, extras,
278                    Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone),
279                    PHONE_LOOKUP_PROJECTION, null, null, null);
280        } else {
281            // If a contact hasn't been assigned, don't react to click.
282            return;
283        }
284    }
285
286    @Override
287    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
288        super.onInitializeAccessibilityEvent(event);
289        event.setClassName(QuickContactBadge.class.getName());
290    }
291
292    @Override
293    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
294        super.onInitializeAccessibilityNodeInfo(info);
295        info.setClassName(QuickContactBadge.class.getName());
296    }
297
298    /**
299     * Set a list of specific MIME-types to exclude and not display. For
300     * example, this can be used to hide the {@link Contacts#CONTENT_ITEM_TYPE}
301     * profile icon.
302     */
303    public void setExcludeMimes(String[] excludeMimes) {
304        mExcludeMimes = excludeMimes;
305    }
306
307    private class QueryHandler extends AsyncQueryHandler {
308
309        public QueryHandler(ContentResolver cr) {
310            super(cr);
311        }
312
313        @Override
314        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
315            Uri lookupUri = null;
316            Uri createUri = null;
317            boolean trigger = false;
318            Bundle extras = (cookie != null) ? (Bundle) cookie : new Bundle();
319            try {
320                switch(token) {
321                    case TOKEN_PHONE_LOOKUP_AND_TRIGGER:
322                        trigger = true;
323                        createUri = Uri.fromParts("tel", extras.getString(EXTRA_URI_CONTENT), null);
324
325                        //$FALL-THROUGH$
326                    case TOKEN_PHONE_LOOKUP: {
327                        if (cursor != null && cursor.moveToFirst()) {
328                            long contactId = cursor.getLong(PHONE_ID_COLUMN_INDEX);
329                            String lookupKey = cursor.getString(PHONE_LOOKUP_STRING_COLUMN_INDEX);
330                            lookupUri = Contacts.getLookupUri(contactId, lookupKey);
331                        }
332
333                        break;
334                    }
335                    case TOKEN_EMAIL_LOOKUP_AND_TRIGGER:
336                        trigger = true;
337                        createUri = Uri.fromParts("mailto",
338                                extras.getString(EXTRA_URI_CONTENT), null);
339
340                        //$FALL-THROUGH$
341                    case TOKEN_EMAIL_LOOKUP: {
342                        if (cursor != null && cursor.moveToFirst()) {
343                            long contactId = cursor.getLong(EMAIL_ID_COLUMN_INDEX);
344                            String lookupKey = cursor.getString(EMAIL_LOOKUP_STRING_COLUMN_INDEX);
345                            lookupUri = Contacts.getLookupUri(contactId, lookupKey);
346                        }
347                        break;
348                    }
349                }
350            } finally {
351                if (cursor != null) {
352                    cursor.close();
353                }
354            }
355
356            mContactUri = lookupUri;
357            onContactUriChanged();
358
359            if (trigger && lookupUri != null) {
360                // Found contact, so trigger QuickContact
361                QuickContact.showQuickContact(getContext(), QuickContactBadge.this, lookupUri,
362                        QuickContact.MODE_LARGE, mExcludeMimes);
363            } else if (createUri != null) {
364                // Prompt user to add this person to contacts
365                final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, createUri);
366                if (extras != null) {
367                    extras.remove(EXTRA_URI_CONTENT);
368                    intent.putExtras(extras);
369                }
370                getContext().startActivity(intent);
371            }
372        }
373    }
374}
375