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