blob: ea39f6d715739bce4cf7434cbd58f7d972cbcd5d [file] [log] [blame]
Aurimas Liutikas93554f22022-04-19 16:51:35 -07001/*
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 android.compat.annotation.UnsupportedAppUsage;
20import android.content.AsyncQueryHandler;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.res.TypedArray;
25import android.database.Cursor;
26import android.graphics.Canvas;
27import android.graphics.drawable.Drawable;
28import android.net.Uri;
29import android.os.Bundle;
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;
39
40import com.android.internal.R;
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 @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
80 public QuickContactBadge(Context context) {
81 this(context, null);
82 }
83
84 public QuickContactBadge(Context context, AttributeSet attrs) {
85 this(context, attrs, 0);
86 }
87
88 public QuickContactBadge(Context context, AttributeSet attrs, int defStyleAttr) {
89 this(context, attrs, defStyleAttr, 0);
90 }
91
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
105 protected void onAttachedToWindow() {
106 super.onAttachedToWindow();
107
108 if (!isInEditMode()) {
109 mQueryHandler = new QueryHandler(mContext.getContentResolver());
110 }
111 }
112
113 @Override
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
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")
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 */
143 public void setPrioritizedMimeType(String prioritizedMimeType) {
144 mPrioritizedMimeType = prioritizedMimeType;
145 }
146
147 @Override
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 */
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 */
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 */
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 */
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
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 */
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 */
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 */
292 public void setOverlay(Drawable overlay) {
293 mOverlay = overlay;
294 }
295
296 private void onContactUriChanged() {
297 setEnabled(isAssigned());
298 }
299
300 @Override
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
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 */
335 public void setExcludeMimes(String[] excludeMimes) {
336 mExcludeMimes = excludeMimes;
337 }
338
339 private class QueryHandler extends AsyncQueryHandler {
340
341 public QueryHandler(ContentResolver cr) {
342 super(cr);
343 }
344
345 @Override
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}