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