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.provider.ContactsContract.CommonDataKinds.Email; 31import android.provider.ContactsContract.Contacts; 32import android.provider.ContactsContract.Intents; 33import android.provider.ContactsContract.PhoneLookup; 34import android.provider.ContactsContract.QuickContact; 35import android.provider.ContactsContract.RawContacts; 36import android.util.AttributeSet; 37import android.view.View; 38import android.view.View.OnClickListener; 39import android.view.accessibility.AccessibilityEvent; 40import android.view.accessibility.AccessibilityNodeInfo; 41 42/** 43 * Widget used to show an image with the standard QuickContact badge 44 * and on-click behavior. 45 */ 46public class QuickContactBadge extends ImageView implements OnClickListener { 47 private Uri mContactUri; 48 private String mContactEmail; 49 private String mContactPhone; 50 private Drawable mOverlay; 51 private QueryHandler mQueryHandler; 52 private Drawable mDefaultAvatar; 53 54 protected String[] mExcludeMimes = null; 55 56 static final private int TOKEN_EMAIL_LOOKUP = 0; 57 static final private int TOKEN_PHONE_LOOKUP = 1; 58 static final private int TOKEN_EMAIL_LOOKUP_AND_TRIGGER = 2; 59 static final private int TOKEN_PHONE_LOOKUP_AND_TRIGGER = 3; 60 61 static final String[] EMAIL_LOOKUP_PROJECTION = new String[] { 62 RawContacts.CONTACT_ID, 63 Contacts.LOOKUP_KEY, 64 }; 65 static final int EMAIL_ID_COLUMN_INDEX = 0; 66 static final int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1; 67 68 static final String[] PHONE_LOOKUP_PROJECTION = new String[] { 69 PhoneLookup._ID, 70 PhoneLookup.LOOKUP_KEY, 71 }; 72 static final int PHONE_ID_COLUMN_INDEX = 0; 73 static final int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1; 74 75 public QuickContactBadge(Context context) { 76 this(context, null); 77 } 78 79 public QuickContactBadge(Context context, AttributeSet attrs) { 80 this(context, attrs, 0); 81 } 82 83 public QuickContactBadge(Context context, AttributeSet attrs, int defStyle) { 84 super(context, attrs, defStyle); 85 86 TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme); 87 mOverlay = styledAttributes.getDrawable( 88 com.android.internal.R.styleable.Theme_quickContactBadgeOverlay); 89 styledAttributes.recycle(); 90 91 mQueryHandler = new QueryHandler(mContext.getContentResolver()); 92 setOnClickListener(this); 93 } 94 95 @Override 96 protected void drawableStateChanged() { 97 super.drawableStateChanged(); 98 if (mOverlay != null && mOverlay.isStateful()) { 99 mOverlay.setState(getDrawableState()); 100 invalidate(); 101 } 102 } 103 104 /** This call has no effect anymore, as there is only one QuickContact mode */ 105 @SuppressWarnings("unused") 106 public void setMode(int size) { 107 } 108 109 @Override 110 protected void onDraw(Canvas canvas) { 111 super.onDraw(canvas); 112 113 if (!isEnabled()) { 114 // not clickable? don't show triangle 115 return; 116 } 117 118 if (mOverlay == null || mOverlay.getIntrinsicWidth() == 0 || 119 mOverlay.getIntrinsicHeight() == 0) { 120 // nothing to draw 121 return; 122 } 123 124 mOverlay.setBounds(0, 0, getWidth(), getHeight()); 125 126 if (mPaddingTop == 0 && mPaddingLeft == 0) { 127 mOverlay.draw(canvas); 128 } else { 129 int saveCount = canvas.getSaveCount(); 130 canvas.save(); 131 canvas.translate(mPaddingLeft, mPaddingTop); 132 mOverlay.draw(canvas); 133 canvas.restoreToCount(saveCount); 134 } 135 } 136 137 /** True if a contact, an email address or a phone number has been assigned */ 138 private boolean isAssigned() { 139 return mContactUri != null || mContactEmail != null || mContactPhone != null; 140 } 141 142 /** 143 * Resets the contact photo to the default state. 144 */ 145 public void setImageToDefault() { 146 if (mDefaultAvatar == null) { 147 mDefaultAvatar = getResources().getDrawable(R.drawable.ic_contact_picture); 148 } 149 setImageDrawable(mDefaultAvatar); 150 } 151 152 /** 153 * Assign the contact uri that this QuickContactBadge should be associated 154 * with. Note that this is only used for displaying the QuickContact window and 155 * won't bind the contact's photo for you. Call {@link #setImageDrawable(Drawable)} to set the 156 * photo. 157 * 158 * @param contactUri Either a {@link Contacts#CONTENT_URI} or 159 * {@link Contacts#CONTENT_LOOKUP_URI} style URI. 160 */ 161 public void assignContactUri(Uri contactUri) { 162 mContactUri = contactUri; 163 mContactEmail = null; 164 mContactPhone = null; 165 onContactUriChanged(); 166 } 167 168 /** 169 * Assign a contact based on an email address. This should only be used when 170 * the contact's URI is not available, as an extra query will have to be 171 * performed to lookup the URI based on the email. 172 * 173 * @param emailAddress The email address of the contact. 174 * @param lazyLookup If this is true, the lookup query will not be performed 175 * until this view is clicked. 176 */ 177 public void assignContactFromEmail(String emailAddress, boolean lazyLookup) { 178 mContactEmail = emailAddress; 179 if (!lazyLookup) { 180 mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, null, 181 Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)), 182 EMAIL_LOOKUP_PROJECTION, null, null, null); 183 } else { 184 mContactUri = null; 185 onContactUriChanged(); 186 } 187 } 188 189 /** 190 * Assign a contact based on a phone number. This should only be used when 191 * the contact's URI is not available, as an extra query will have to be 192 * performed to lookup the URI based on the phone number. 193 * 194 * @param phoneNumber The phone number of the contact. 195 * @param lazyLookup If this is true, the lookup query will not be performed 196 * until this view is clicked. 197 */ 198 public void assignContactFromPhone(String phoneNumber, boolean lazyLookup) { 199 mContactPhone = phoneNumber; 200 if (!lazyLookup) { 201 mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, null, 202 Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone), 203 PHONE_LOOKUP_PROJECTION, null, null, null); 204 } else { 205 mContactUri = null; 206 onContactUriChanged(); 207 } 208 } 209 210 private void onContactUriChanged() { 211 setEnabled(isAssigned()); 212 } 213 214 @Override 215 public void onClick(View v) { 216 if (mContactUri != null) { 217 QuickContact.showQuickContact(getContext(), QuickContactBadge.this, mContactUri, 218 QuickContact.MODE_LARGE, mExcludeMimes); 219 } else if (mContactEmail != null) { 220 mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, mContactEmail, 221 Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)), 222 EMAIL_LOOKUP_PROJECTION, null, null, null); 223 } else if (mContactPhone != null) { 224 mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP_AND_TRIGGER, mContactPhone, 225 Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone), 226 PHONE_LOOKUP_PROJECTION, null, null, null); 227 } else { 228 // If a contact hasn't been assigned, don't react to click. 229 return; 230 } 231 } 232 233 @Override 234 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 235 super.onInitializeAccessibilityEvent(event); 236 event.setClassName(QuickContactBadge.class.getName()); 237 } 238 239 @Override 240 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 241 super.onInitializeAccessibilityNodeInfo(info); 242 info.setClassName(QuickContactBadge.class.getName()); 243 } 244 245 /** 246 * Set a list of specific MIME-types to exclude and not display. For 247 * example, this can be used to hide the {@link Contacts#CONTENT_ITEM_TYPE} 248 * profile icon. 249 */ 250 public void setExcludeMimes(String[] excludeMimes) { 251 mExcludeMimes = excludeMimes; 252 } 253 254 private class QueryHandler extends AsyncQueryHandler { 255 256 public QueryHandler(ContentResolver cr) { 257 super(cr); 258 } 259 260 @Override 261 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 262 Uri lookupUri = null; 263 Uri createUri = null; 264 boolean trigger = false; 265 266 try { 267 switch(token) { 268 case TOKEN_PHONE_LOOKUP_AND_TRIGGER: 269 trigger = true; 270 createUri = Uri.fromParts("tel", (String)cookie, null); 271 272 //$FALL-THROUGH$ 273 case TOKEN_PHONE_LOOKUP: { 274 if (cursor != null && cursor.moveToFirst()) { 275 long contactId = cursor.getLong(PHONE_ID_COLUMN_INDEX); 276 String lookupKey = cursor.getString(PHONE_LOOKUP_STRING_COLUMN_INDEX); 277 lookupUri = Contacts.getLookupUri(contactId, lookupKey); 278 } 279 280 break; 281 } 282 case TOKEN_EMAIL_LOOKUP_AND_TRIGGER: 283 trigger = true; 284 createUri = Uri.fromParts("mailto", (String)cookie, null); 285 286 //$FALL-THROUGH$ 287 case TOKEN_EMAIL_LOOKUP: { 288 if (cursor != null && cursor.moveToFirst()) { 289 long contactId = cursor.getLong(EMAIL_ID_COLUMN_INDEX); 290 String lookupKey = cursor.getString(EMAIL_LOOKUP_STRING_COLUMN_INDEX); 291 lookupUri = Contacts.getLookupUri(contactId, lookupKey); 292 } 293 break; 294 } 295 } 296 } finally { 297 if (cursor != null) { 298 cursor.close(); 299 } 300 } 301 302 mContactUri = lookupUri; 303 onContactUriChanged(); 304 305 if (trigger && lookupUri != null) { 306 // Found contact, so trigger QuickContact 307 QuickContact.showQuickContact(getContext(), QuickContactBadge.this, lookupUri, 308 QuickContact.MODE_LARGE, mExcludeMimes); 309 } else if (createUri != null) { 310 // Prompt user to add this person to contacts 311 final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, createUri); 312 getContext().startActivity(intent); 313 } 314 } 315 } 316} 317