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