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