blob: 4987775c7f674461e9892504fe98aceb65a42a1c [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.widget;
import android.Manifest;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.net.Uri;
import android.os.SystemClock;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.Presence;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.SocialContract.Activities;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.FasttrackBadgeWidget;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.R;
/**
* Header used across system for displaying a title bar with contact info. You
* can bind specific values on the header, or use helper methods like
* {@link #bindFromContactId(long)} to populate asynchronously.
* <p>
* The parent must request the {@link Manifest.permission#READ_CONTACTS}
* permission to access contact data.
*/
public class ContactHeaderWidget extends FrameLayout implements View.OnClickListener,
View.OnLongClickListener {
private static final String TAG = "ContactHeaderWidget";
private TextView mDisplayNameView;
private TextView mPhoneticNameView;
private CheckBox mStarredView;
private FasttrackBadgeWidget mPhotoView;
private ImageView mPresenceView;
private TextView mStatusView;
private int mNoPhotoResource;
private QueryHandler mQueryHandler;
protected Uri mContactUri;
protected String[] mExcludeMimes = null;
protected ContentResolver mContentResolver;
/**
* Interface for callbacks invoked when the user interacts with a header.
*/
public interface ContactHeaderListener {
public void onPhotoLongClick(View view);
public void onDisplayNameLongClick(View view);
}
private ContactHeaderListener mListener;
//Projection used for the summary info in the header.
protected static final String[] HEADER_PROJECTION = new String[] {
Contacts.DISPLAY_NAME,
Contacts.STARRED,
Contacts.PHOTO_ID,
Contacts.PRESENCE_STATUS,
Contacts._ID,
Contacts.LOOKUP_KEY,
};
protected static final int HEADER_DISPLAY_NAME_COLUMN_INDEX = 0;
//TODO: We need to figure out how we're going to get the phonetic name.
//static final int HEADER_PHONETIC_NAME_COLUMN_INDEX
protected static final int HEADER_STARRED_COLUMN_INDEX = 1;
protected static final int HEADER_PHOTO_ID_COLUMN_INDEX = 2;
protected static final int HEADER_PRESENCE_STATUS_COLUMN_INDEX = 3;
protected static final int HEADER_CONTACT_ID_COLUMN_INDEX = 4;
protected static final int HEADER_LOOKUP_KEY_COLUMN_INDEX = 5;
//Projection used for finding the most recent social status.
protected static final String[] SOCIAL_PROJECTION = new String[] {
Activities.TITLE,
Activities.PUBLISHED,
};
protected static final int SOCIAL_TITLE_COLUMN_INDEX = 0;
protected static final int SOCIAL_PUBLISHED_COLUMN_INDEX = 1;
//Projection used for looking up contact id from phone number
protected static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
PhoneLookup._ID,
PhoneLookup.LOOKUP_KEY,
};
protected static final int PHONE_LOOKUP_CONTACT_ID_COLUMN_INDEX = 0;
protected static final int PHONE_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX = 1;
//Projection used for looking up contact id from email address
protected static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
RawContacts.CONTACT_ID,
Contacts.LOOKUP_KEY,
};
protected static final int EMAIL_LOOKUP_CONTACT_ID_COLUMN_INDEX = 0;
protected static final int EMAIL_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX = 1;
protected static final String[] CONTACT_LOOKUP_PROJECTION = new String[] {
Contacts._ID,
};
protected static final int CONTACT_LOOKUP_ID_COLUMN_INDEX = 0;
private static final int TOKEN_CONTACT_INFO = 0;
private static final int TOKEN_SOCIAL = 1;
private static final int TOKEN_PHONE_LOOKUP = 2;
private static final int TOKEN_EMAIL_LOOKUP = 3;
private static final int TOKEN_LOOKUP_CONTACT_FOR_SOCIAL_QUERY = 4;
public ContactHeaderWidget(Context context) {
this(context, null);
}
public ContactHeaderWidget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ContactHeaderWidget(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContentResolver = mContext.getContentResolver();
LayoutInflater inflater =
(LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.contact_header, this);
mDisplayNameView = (TextView) findViewById(R.id.name);
mDisplayNameView.setOnLongClickListener(this);
mPhoneticNameView = (TextView) findViewById(R.id.phonetic_name);
mStarredView = (CheckBox)findViewById(R.id.star);
mStarredView.setOnClickListener(this);
mPhotoView = (FasttrackBadgeWidget) findViewById(R.id.photo);
mPhotoView.setOnLongClickListener(this);
mPresenceView = (ImageView) findViewById(R.id.presence);
mStatusView = (TextView)findViewById(R.id.status);
// Set the photo with a random "no contact" image
long now = SystemClock.elapsedRealtime();
int num = (int) now & 0xf;
if (num < 9) {
// Leaning in from right, common
mNoPhotoResource = R.drawable.ic_contact_picture;
} else if (num < 14) {
// Leaning in from left uncommon
mNoPhotoResource = R.drawable.ic_contact_picture_2;
} else {
// Coming in from the top, rare
mNoPhotoResource = R.drawable.ic_contact_picture_3;
}
mQueryHandler = new QueryHandler(mContentResolver);
}
/**
* Set the given {@link ContactHeaderListener} to handle header events.
*/
public void setContactHeaderListener(ContactHeaderListener listener) {
mListener = listener;
}
/** {@inheritDoc} */
public boolean onLongClick(View v) {
switch (v.getId()) {
case R.id.photo:
performPhotoLongClick();
return true;
case R.id.name:
performDisplayNameLongClick();
return true;
}
return false;
}
private void performPhotoLongClick() {
if (mListener != null) {
mListener.onPhotoLongClick(mPhotoView);
}
}
private void performDisplayNameLongClick() {
if (mListener != null) {
mListener.onDisplayNameLongClick(mDisplayNameView);
}
}
private class QueryHandler extends AsyncQueryHandler {
public QueryHandler(ContentResolver cr) {
super(cr);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
try{
switch (token) {
case TOKEN_CONTACT_INFO: {
bindContactInfo(cursor);
invalidate();
break;
}
case TOKEN_SOCIAL: {
bindSocial(cursor);
invalidate();
break;
}
case TOKEN_PHONE_LOOKUP: {
if (cursor != null && cursor.moveToFirst()) {
long contactId = cursor.getLong(PHONE_LOOKUP_CONTACT_ID_COLUMN_INDEX);
String lookupKey = cursor.getString(
PHONE_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX);
bindFromContactUri(Contacts.getLookupUri(contactId, lookupKey));
} else {
String phoneNumber = (String) cookie;
setDisplayName(phoneNumber, null);
mPhotoView.assignContactFromPhone(phoneNumber, true);
}
break;
}
case TOKEN_EMAIL_LOOKUP: {
if (cursor != null && cursor.moveToFirst()) {
long contactId = cursor.getLong(EMAIL_LOOKUP_CONTACT_ID_COLUMN_INDEX);
String lookupKey = cursor.getString(
EMAIL_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX);
bindFromContactUri(Contacts.getLookupUri(contactId, lookupKey));
} else {
String emailAddress = (String) cookie;
setDisplayName(emailAddress, null);
mPhotoView.assignContactFromEmail(emailAddress, true);
}
break;
}
case TOKEN_LOOKUP_CONTACT_FOR_SOCIAL_QUERY: {
if (cursor != null && cursor.moveToFirst()) {
long contactId = cursor.getLong(CONTACT_LOOKUP_ID_COLUMN_INDEX);
startSocialQuery(ContentUris.withAppendedId(
Activities.CONTENT_CONTACT_STATUS_URI, contactId));
}
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
}
/**
* Turn on/off showing of the star element.
*/
public void showStar(boolean showStar) {
mStarredView.setVisibility(showStar ? View.VISIBLE : View.GONE);
}
/**
* Manually set the starred state of this header widget. This doesn't change
* the underlying {@link Contacts} value, only the UI state.
*/
public void setStared(boolean starred) {
mStarredView.setChecked(starred);
}
/**
* Manually set the presence.
*/
public void setPresence(int presence) {
mPresenceView.setImageResource(Presence.getPresenceIconResourceId(presence));
}
/**
* Manually set the contact uri
*/
public void setContactUri(Uri uri) {
mContactUri = uri;
}
/**
* Manually set the photo to display in the header. This doesn't change the
* underlying {@link Contacts}, only the UI state.
*/
public void setPhoto(Bitmap bitmap) {
mPhotoView.setImageBitmap(bitmap);
}
/**
* Manually set the display name and phonetic name to show in the header.
* This doesn't change the underlying {@link Contacts}, only the UI state.
*/
public void setDisplayName(CharSequence displayName, CharSequence phoneticName) {
mDisplayNameView.setText(displayName);
if (mPhoneticNameView != null) {
mPhoneticNameView.setText(phoneticName);
}
}
/**
* Manually set the social snippet text to display in the header.
*/
public void setSocialSnippet(CharSequence snippet) {
mStatusView.setText(snippet);
}
/**
* Set a list of specific MIME-types to exclude and not display. For
* example, this can be used to hide the {@link Contacts#CONTENT_ITEM_TYPE}
* profile icon.
*/
public void setExcludeMimes(String[] excludeMimes) {
mExcludeMimes = excludeMimes;
}
/**
* Convenience method for binding all available data from an existing
* contact.
*
* @param conatctUri a {Contacts.CONTENT_LOOKUP_URI} style URI.
*/
public void bindFromContactLookupUri(Uri contactLookupUri) {
mContactUri = contactLookupUri;
// Query for the contactId so we can do the social query.
mQueryHandler.startQuery(TOKEN_LOOKUP_CONTACT_FOR_SOCIAL_QUERY, null, contactLookupUri,
CONTACT_LOOKUP_PROJECTION, null, null, null);
startContactQuery(contactLookupUri);
}
/**
* Convenience method for binding all available data from an existing
* contact.
*
* @param conatctUri a {Contacts.CONTENT_URI} style URI.
*/
public void bindFromContactUri(Uri contactUri) {
mContactUri = contactUri;
long contactId = ContentUris.parseId(contactUri);
startContactQuery(contactUri);
startSocialQuery(ContentUris.withAppendedId(
Activities.CONTENT_CONTACT_STATUS_URI, contactId));
}
/**
* Convenience method for binding all available data from an existing
* contact.
*
* @param emailAddress The email address used to do a reverse lookup in
* the contacts database. If more than one contact contains this email
* address, one of them will be chosen to bind to.
*/
public void bindFromEmail(String emailAddress) {
mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, emailAddress,
Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(emailAddress)),
EMAIL_LOOKUP_PROJECTION, null, null, null);
}
/**
* Convenience method for binding all available data from an existing
* contact.
*
* @param number The phone number used to do a reverse lookup in
* the contacts database. If more than one contact contains this phone
* number, one of them will be chosen to bind to.
*/
public void bindFromPhoneNumber(String number) {
mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, number,
Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
PHONE_LOOKUP_PROJECTION, null, null, null);
}
private void startSocialQuery(Uri contactSocial) {
mQueryHandler.startQuery(TOKEN_SOCIAL, null, contactSocial, SOCIAL_PROJECTION, null, null,
null);
}
private void startContactQuery(Uri contactUri) {
mQueryHandler.startQuery(TOKEN_CONTACT_INFO, null, contactUri, HEADER_PROJECTION,
null, null, null);
}
/**
* Bind the contact details provided by the given {@link Cursor}.
*/
protected void bindContactInfo(Cursor c) {
if (c == null || !c.moveToFirst()) return;
// TODO: Bring back phonetic name
final String displayName = c.getString(HEADER_DISPLAY_NAME_COLUMN_INDEX);
final long contactId = c.getLong(HEADER_CONTACT_ID_COLUMN_INDEX);
final String lookupKey = c.getString(HEADER_LOOKUP_KEY_COLUMN_INDEX);
final String phoneticName = null;
this.setDisplayName(displayName, null);
final boolean starred = c.getInt(HEADER_STARRED_COLUMN_INDEX) != 0;
mStarredView.setChecked(starred);
//Set the photo
Bitmap photoBitmap = loadContactPhoto(c.getLong(HEADER_PHOTO_ID_COLUMN_INDEX), null);
if (photoBitmap == null) {
photoBitmap = loadPlaceholderPhoto(null);
}
mPhotoView.setImageBitmap(photoBitmap);
mPhotoView.assignContactUri(Contacts.getLookupUri(contactId, lookupKey));
//Set the presence status
int presence = c.getInt(HEADER_PRESENCE_STATUS_COLUMN_INDEX);
mPresenceView.setImageResource(Presence.getPresenceIconResourceId(presence));
}
/**
* Bind the social data provided by the given {@link Cursor}.
*/
protected void bindSocial(Cursor c) {
if (c == null || !c.moveToFirst()) return;
final String status = c.getString(SOCIAL_TITLE_COLUMN_INDEX);
this.setSocialSnippet(status);
}
public void onClick(View view) {
// Make sure there is a contact
if (mContactUri == null) {
return;
}
if (view.getId() == R.id.star) {
// Toggle "starred" state
final ContentValues values = new ContentValues(1);
values.put(Contacts.STARRED, mStarredView.isChecked());
mContentResolver.update(mContactUri, values, null, null);
}
}
private Rect getTargetRect(View anchor) {
final int[] location = new int[2];
anchor.getLocationOnScreen(location);
final Rect rect = new Rect();
rect.left = location[0];
rect.top = location[1];
rect.right = rect.left + anchor.getWidth();
rect.bottom = rect.top + anchor.getHeight();
return rect;
}
private Bitmap loadContactPhoto(long photoId, BitmapFactory.Options options) {
Cursor photoCursor = null;
Bitmap photoBm = null;
try {
photoCursor = mContentResolver.query(
ContentUris.withAppendedId(Data.CONTENT_URI, photoId),
new String[] { Photo.PHOTO },
null, null, null);
if (photoCursor != null && photoCursor.moveToFirst() && !photoCursor.isNull(0)) {
byte[] photoData = photoCursor.getBlob(0);
photoBm = BitmapFactory.decodeByteArray(photoData, 0,
photoData.length, options);
}
} finally {
if (photoCursor != null) {
photoCursor.close();
}
}
return photoBm;
}
private Bitmap loadPlaceholderPhoto(BitmapFactory.Options options) {
if (mNoPhotoResource == 0) {
return null;
}
return BitmapFactory.decodeResource(mContext.getResources(),
mNoPhotoResource, options);
}
}