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