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