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 import android.view.accessibility.AccessibilityEvent;
     41 import android.view.accessibility.AccessibilityNodeInfo;
     42 
     43 /**
     44  * Widget used to show an image with the standard QuickContact badge
     45  * and on-click behavior.
     46  */
     47 public 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 defStyle) {
     88         super(context, attrs, defStyle);
     89 
     90         TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme);
     91         mOverlay = styledAttributes.getDrawable(
     92                 com.android.internal.R.styleable.Theme_quickContactBadgeOverlay);
     93         styledAttributes.recycle();
     94 
     95         if (!isInEditMode()) {
     96             mQueryHandler = new QueryHandler(mContext.getContentResolver());
     97         }
     98         setOnClickListener(this);
     99     }
    100 
    101     @Override
    102     protected void drawableStateChanged() {
    103         super.drawableStateChanged();
    104         if (mOverlay != null && mOverlay.isStateful()) {
    105             mOverlay.setState(getDrawableState());
    106             invalidate();
    107         }
    108     }
    109 
    110     /** This call has no effect anymore, as there is only one QuickContact mode */
    111     @SuppressWarnings("unused")
    112     public void setMode(int size) {
    113     }
    114 
    115     @Override
    116     protected void onDraw(Canvas canvas) {
    117         super.onDraw(canvas);
    118 
    119         if (!isEnabled()) {
    120             // not clickable? don't show triangle
    121             return;
    122         }
    123 
    124         if (mOverlay == null || mOverlay.getIntrinsicWidth() == 0 ||
    125                 mOverlay.getIntrinsicHeight() == 0) {
    126             // nothing to draw
    127             return;
    128         }
    129 
    130         mOverlay.setBounds(0, 0, getWidth(), getHeight());
    131 
    132         if (mPaddingTop == 0 && mPaddingLeft == 0) {
    133             mOverlay.draw(canvas);
    134         } else {
    135             int saveCount = canvas.getSaveCount();
    136             canvas.save();
    137             canvas.translate(mPaddingLeft, mPaddingTop);
    138             mOverlay.draw(canvas);
    139             canvas.restoreToCount(saveCount);
    140         }
    141     }
    142 
    143     /** True if a contact, an email address or a phone number has been assigned */
    144     private boolean isAssigned() {
    145         return mContactUri != null || mContactEmail != null || mContactPhone != null;
    146     }
    147 
    148     /**
    149      * Resets the contact photo to the default state.
    150      */
    151     public void setImageToDefault() {
    152         if (mDefaultAvatar == null) {
    153             mDefaultAvatar = getResources().getDrawable(R.drawable.ic_contact_picture);
    154         }
    155         setImageDrawable(mDefaultAvatar);
    156     }
    157 
    158     /**
    159      * Assign the contact uri that this QuickContactBadge should be associated
    160      * with. Note that this is only used for displaying the QuickContact window and
    161      * won't bind the contact's photo for you. Call {@link #setImageDrawable(Drawable)} to set the
    162      * photo.
    163      *
    164      * @param contactUri Either a {@link Contacts#CONTENT_URI} or
    165      *            {@link Contacts#CONTENT_LOOKUP_URI} style URI.
    166      */
    167     public void assignContactUri(Uri contactUri) {
    168         mContactUri = contactUri;
    169         mContactEmail = null;
    170         mContactPhone = null;
    171         onContactUriChanged();
    172     }
    173 
    174     /**
    175      * Assign a contact based on an email address. This should only be used when
    176      * the contact's URI is not available, as an extra query will have to be
    177      * performed to lookup the URI based on the email.
    178      *
    179      * @param emailAddress The email address of the contact.
    180      * @param lazyLookup If this is true, the lookup query will not be performed
    181      * until this view is clicked.
    182      */
    183     public void assignContactFromEmail(String emailAddress, boolean lazyLookup) {
    184         assignContactFromEmail(emailAddress, lazyLookup, null);
    185     }
    186 
    187     /**
    188      * Assign a contact based on an email address. This should only be used when
    189      * the contact's URI is not available, as an extra query will have to be
    190      * performed to lookup the URI based on the email.
    191 
    192      @param emailAddress The email address of the contact.
    193      @param lazyLookup If this is true, the lookup query will not be performed
    194      until this view is clicked.
    195      @param extras A bundle of extras to populate the contact edit page with if the contact
    196      is not found and the user chooses to add the email address to an existing contact or
    197      create a new contact. Uses the same string constants as those found in
    198      {@link android.provider.ContactsContract.Intents.Insert}
    199     */
    200 
    201     public void assignContactFromEmail(String emailAddress, boolean lazyLookup, Bundle extras) {
    202         mContactEmail = emailAddress;
    203         mExtras = extras;
    204         if (!lazyLookup && mQueryHandler != null) {
    205             mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, null,
    206                     Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
    207                     EMAIL_LOOKUP_PROJECTION, null, null, null);
    208         } else {
    209             mContactUri = null;
    210             onContactUriChanged();
    211         }
    212     }
    213 
    214 
    215     /**
    216      * Assign a contact based on a phone number. This should only be used when
    217      * the contact's URI is not available, as an extra query will have to be
    218      * performed to lookup the URI based on the phone number.
    219      *
    220      * @param phoneNumber The phone number of the contact.
    221      * @param lazyLookup If this is true, the lookup query will not be performed
    222      * until this view is clicked.
    223      */
    224     public void assignContactFromPhone(String phoneNumber, boolean lazyLookup) {
    225         assignContactFromPhone(phoneNumber, lazyLookup, new Bundle());
    226     }
    227 
    228     /**
    229      * Assign a contact based on a phone number. This should only be used when
    230      * the contact's URI is not available, as an extra query will have to be
    231      * performed to lookup the URI based on the phone number.
    232      *
    233      * @param phoneNumber The phone number of the contact.
    234      * @param lazyLookup If this is true, the lookup query will not be performed
    235      * until this view is clicked.
    236      * @param extras A bundle of extras to populate the contact edit page with if the contact
    237      * is not found and the user chooses to add the phone number to an existing contact or
    238      * create a new contact. Uses the same string constants as those found in
    239      * {@link android.provider.ContactsContract.Intents.Insert}
    240      */
    241     public void assignContactFromPhone(String phoneNumber, boolean lazyLookup, Bundle extras) {
    242         mContactPhone = phoneNumber;
    243         mExtras = extras;
    244         if (!lazyLookup && mQueryHandler != null) {
    245             mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, null,
    246                     Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone),
    247                     PHONE_LOOKUP_PROJECTION, null, null, null);
    248         } else {
    249             mContactUri = null;
    250             onContactUriChanged();
    251         }
    252     }
    253 
    254     private void onContactUriChanged() {
    255         setEnabled(isAssigned());
    256     }
    257 
    258     @Override
    259     public void onClick(View v) {
    260         // If contact has been assigned, mExtras should no longer be null, but do a null check
    261         // anyway just in case assignContactFromPhone or Email was called with a null bundle or
    262         // wasn't assigned previously.
    263         final Bundle extras = (mExtras == null) ? new Bundle() : mExtras;
    264         if (mContactUri != null) {
    265             QuickContact.showQuickContact(getContext(), QuickContactBadge.this, mContactUri,
    266                     QuickContact.MODE_LARGE, mExcludeMimes);
    267         } else if (mContactEmail != null && mQueryHandler != null) {
    268             extras.putString(EXTRA_URI_CONTENT, mContactEmail);
    269             mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, extras,
    270                     Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
    271                     EMAIL_LOOKUP_PROJECTION, null, null, null);
    272         } else if (mContactPhone != null && mQueryHandler != null) {
    273             extras.putString(EXTRA_URI_CONTENT, mContactPhone);
    274             mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP_AND_TRIGGER, extras,
    275                     Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone),
    276                     PHONE_LOOKUP_PROJECTION, null, null, null);
    277         } else {
    278             // If a contact hasn't been assigned, don't react to click.
    279             return;
    280         }
    281     }
    282 
    283     @Override
    284     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    285         super.onInitializeAccessibilityEvent(event);
    286         event.setClassName(QuickContactBadge.class.getName());
    287     }
    288 
    289     @Override
    290     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    291         super.onInitializeAccessibilityNodeInfo(info);
    292         info.setClassName(QuickContactBadge.class.getName());
    293     }
    294 
    295     /**
    296      * Set a list of specific MIME-types to exclude and not display. For
    297      * example, this can be used to hide the {@link Contacts#CONTENT_ITEM_TYPE}
    298      * profile icon.
    299      */
    300     public void setExcludeMimes(String[] excludeMimes) {
    301         mExcludeMimes = excludeMimes;
    302     }
    303 
    304     private class QueryHandler extends AsyncQueryHandler {
    305 
    306         public QueryHandler(ContentResolver cr) {
    307             super(cr);
    308         }
    309 
    310         @Override
    311         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    312             Uri lookupUri = null;
    313             Uri createUri = null;
    314             boolean trigger = false;
    315             Bundle extras = (cookie != null) ? (Bundle) cookie : new Bundle();
    316             try {
    317                 switch(token) {
    318                     case TOKEN_PHONE_LOOKUP_AND_TRIGGER:
    319                         trigger = true;
    320                         createUri = Uri.fromParts("tel", extras.getString(EXTRA_URI_CONTENT), null);
    321 
    322                         //$FALL-THROUGH$
    323                     case TOKEN_PHONE_LOOKUP: {
    324                         if (cursor != null && cursor.moveToFirst()) {
    325                             long contactId = cursor.getLong(PHONE_ID_COLUMN_INDEX);
    326                             String lookupKey = cursor.getString(PHONE_LOOKUP_STRING_COLUMN_INDEX);
    327                             lookupUri = Contacts.getLookupUri(contactId, lookupKey);
    328                         }
    329 
    330                         break;
    331                     }
    332                     case TOKEN_EMAIL_LOOKUP_AND_TRIGGER:
    333                         trigger = true;
    334                         createUri = Uri.fromParts("mailto",
    335                                 extras.getString(EXTRA_URI_CONTENT), null);
    336 
    337                         //$FALL-THROUGH$
    338                     case TOKEN_EMAIL_LOOKUP: {
    339                         if (cursor != null && cursor.moveToFirst()) {
    340                             long contactId = cursor.getLong(EMAIL_ID_COLUMN_INDEX);
    341                             String lookupKey = cursor.getString(EMAIL_LOOKUP_STRING_COLUMN_INDEX);
    342                             lookupUri = Contacts.getLookupUri(contactId, lookupKey);
    343                         }
    344                         break;
    345                     }
    346                 }
    347             } finally {
    348                 if (cursor != null) {
    349                     cursor.close();
    350                 }
    351             }
    352 
    353             mContactUri = lookupUri;
    354             onContactUriChanged();
    355 
    356             if (trigger && lookupUri != null) {
    357                 // Found contact, so trigger QuickContact
    358                 QuickContact.showQuickContact(getContext(), QuickContactBadge.this, lookupUri,
    359                         QuickContact.MODE_LARGE, mExcludeMimes);
    360             } else if (createUri != null) {
    361                 // Prompt user to add this person to contacts
    362                 final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, createUri);
    363                 if (extras != null) {
    364                     extras.remove(EXTRA_URI_CONTENT);
    365                     intent.putExtras(extras);
    366                 }
    367                 getContext().startActivity(intent);
    368             }
    369         }
    370     }
    371 }
    372