Home | History | Annotate | Download | only in widget
      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 com.android.internal.R;
     20 
     21 import android.content.AsyncQueryHandler;
     22 import android.content.ContentResolver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.res.TypedArray;
     26 import android.database.Cursor;
     27 import android.graphics.Canvas;
     28 import android.graphics.drawable.Drawable;
     29 import android.net.Uri;
     30 import android.os.Bundle;
     31 import android.provider.ContactsContract.CommonDataKinds.Email;
     32 import android.provider.ContactsContract.Contacts;
     33 import android.provider.ContactsContract.Intents;
     34 import android.provider.ContactsContract.PhoneLookup;
     35 import android.provider.ContactsContract.QuickContact;
     36 import android.provider.ContactsContract.RawContacts;
     37 import android.util.AttributeSet;
     38 import android.view.View;
     39 import android.view.View.OnClickListener;
     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 
     78     public QuickContactBadge(Context context) {
     79         this(context, null);
     80     }
     81 
     82     public QuickContactBadge(Context context, AttributeSet attrs) {
     83         this(context, attrs, 0);
     84     }
     85 
     86     public QuickContactBadge(Context context, AttributeSet attrs, int defStyleAttr) {
     87         this(context, attrs, defStyleAttr, 0);
     88     }
     89 
     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
    103     protected void onAttachedToWindow() {
    104         super.onAttachedToWindow();
    105 
    106         if (!isInEditMode()) {
    107             mQueryHandler = new QueryHandler(mContext.getContentResolver());
    108         }
    109     }
    110 
    111     @Override
    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
    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")
    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      */
    141     public void setPrioritizedMimeType(String prioritizedMimeType) {
    142         mPrioritizedMimeType = prioritizedMimeType;
    143     }
    144 
    145     @Override
    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 */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    290     public void setOverlay(Drawable overlay) {
    291         mOverlay = overlay;
    292     }
    293 
    294     private void onContactUriChanged() {
    295         setEnabled(isAssigned());
    296     }
    297 
    298     @Override
    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
    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      */
    333     public void setExcludeMimes(String[] excludeMimes) {
    334         mExcludeMimes = excludeMimes;
    335     }
    336 
    337     private class QueryHandler extends AsyncQueryHandler {
    338 
    339         public QueryHandler(ContentResolver cr) {
    340             super(cr);
    341         }
    342 
    343         @Override
    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