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