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