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