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