/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at
* src/com/vodafone360/people/VODAFONE.LICENSE.txt or
* http://github.com/360/360-Engine-for-Android
* See the License for the specific language governing permissions and
* limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each file and
* include the License file at src/com/vodafone360/people/VODAFONE.LICENSE.txt.
* If applicable, add the following below this CDDL HEADER, with the fields
* enclosed by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
* Copyright 2010 Vodafone Sales & Services Ltd. All rights reserved.
* Use is subject to license terms.
*/
package com.vodafone360.people.datatypes;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import android.os.Parcel;
import android.os.Parcelable;
import com.vodafone360.people.database.persistenceHelper.Persistable;
import com.vodafone360.people.database.persistenceHelper.Persistable.Entity;
import com.vodafone360.people.database.persistenceHelper.Persistable.Table;
import com.vodafone360.people.utils.LogUtils;
/**
* BaseDataType encapsulating a Contact retrieved from, or sent to, Now+ server.
* <p>
* Represents a contact in the people client database with all the associated
* information. Information stored in a contact is fetched from multiple
* sub-tables. For example a contact is made up of multiple details which are
* fetched from the contact details table.
* <p>
* The lighter weight {@link ContactSummary} object should be used if only the
* contact name and essential details are needed.
*/
@Entity
@Table(name = "Contacts")
public class Contact extends BaseDataType implements Parcelable, Persistable {
/**
* Tags for fields associated with Contact items.
*/
private enum Tags {
CONTACT_ID("contactid"),
GENDER("gender"),
DELETED("deleted"),
USER_ID("userid"),
ABOUT_ME("aboutme"),
UPDATED("updated"),
SOURCES("sources"),
DETAIL_LIST("detaillist"),
DETAIL("detail"),
GROUP_LIST("groupidlist"),
FRIEND("friend"),
PROFILE_PATH("profilepath"), // only for user profile
SYNCTOPHONE("synctophone"); // used for 'Phonebook' group
private final String tag;
/**
* Constructor for Tags item.
*
* @param s String value associated with Tag.
*/
private Tags(String s) {
tag = s;
}
/**
* String value associated with Tags item.
*
* @return String value associated with Tags item.
*/
private String tag() {
return tag;
}
}
/**
* Primary key in the database
*/
@Id
@Column(name = "LocalId")
public Long localContactID = null;
/**
* Contains the about me string provided by the server
*/
@Column(name = "AboutMe")
public String aboutMe = null;
/**
* The server ID (or null if the contact has not yet been synchronised)
*/
@Column(name = "ServerId")
public Long contactID = null;
/**
* The user ID if the contact has been sychronised with the server and the
* contact is associated with a user
*/
@Column(name = "UserId")
public Long userID = null;
/**
* A list of sources which has been fetched from the sources sub-table
*/
public List<String> sources = null;
/**
* The timestamp when the contact was last updated on the server
*/
@Column(name = "Updated")
public Long updated = null;
/**
* The path of the contact thumbnail/avatar on the server
*/
public String profilePath = null; // only for user profile
/**
* The gender of the contact received from the server
*/
@Column(name = "Gender")
public Integer gender = null; // only for user profile
/**
* Contains true if the contacts is marked as a friend
*/
@Column(name = "Friend")
public Boolean friendOfMine = null;
/**
* Contains true if the contact has been deleted on the server
*/
public Boolean deleted = null;
/**
* Internal value which is used to store the primary key of the contact
* stored in the native Android addressbook.
*/
@Column(name = "NativeContactId")
public Integer nativeContactId = null;
/**
* A list of contact details (this contains the main information stored in
* the contact such as name, phone number, email, etc.)
*/
public final List<ContactDetail> details = new ArrayList<ContactDetail>();
/**
* A list groups (server IDs) which the contact is a member of
*/
public List<Long> groupList = null;
/**
* Set to true if this contact should be synchronised with the native
* address book.
* <p>
* Also determines which contacts are shown in the phonebook group in the
* UI.
*/
@Column(name = "Synctophone")
public Boolean synctophone = null;
/**
* Find Tags item for specified String
*
* @param tag String value to find Tags item for
* @return Tags item for specified String, null otherwise
*/
private Tags findTag(String tag) {
for (Tags tags : Tags.values()) {
if (tag.compareTo(tags.tag()) == 0) {
return tags;
}
}
LogUtils.logE("Contact.findTag - Unsupported contact tag: " + tag);
return null;
}
/** {@inheritDoc} */
@Override
public int getType() {
return CONTACT_DATA_TYPE;
}
/** {@inheritDoc} */
@Override
public String toString() {
Date time = null;
if (updated != null) {
time = new Date(updated * 1000);
} else {
time = new Date(0);
}
StringBuffer sb = new StringBuffer("--\nContact data:");
sb.append("\n\tLocal Contact ID: "); sb.append(localContactID);
sb.append("\n\tContact ID: "); sb.append(contactID);
sb.append("\n\tUser ID: "); sb.append(userID);
sb.append("\n\tAbout me: "); sb.append(aboutMe);
sb.append("\n\tFriend of mine: "); sb.append(friendOfMine);
sb.append("\n\tDeleted: "); sb.append(deleted);
sb.append("\n\tGender: "); sb.append(gender);
sb.append("\n\tSynctophone: ");
sb.append(synctophone);
sb.append("\n\tNative Contact ID: "); sb.append(nativeContactId);
sb.append("\n\tupdated: "); sb.append(updated);
sb.append(" Date: "); sb.append(time.toGMTString()); sb.append("\n");
if (sources != null) {
sb.append("Sources ("); sb.append(sources.size()); sb.append("): ");
for (int i = 0; i < sources.size(); i++) {
sb.append(sources.get(i)); sb.append(",");
}
sb.append("\n");
}
if (groupList != null) {
sb.append("Group id list = [");
for (int i = 0; i < groupList.size(); i++) {
sb.append(groupList.get(i));
if (i < groupList.size() - 1) {
sb.append(",");
}
}
sb.append("\n");
}
sb.append("Contact details (");
sb.append(details.size()); sb.append("):\n");
for (int i = 0; i < details.size(); i++) {
sb.append(details.get(i).toString());
sb.append("\n");
}
sb.append("\n--------------------------------------------------");
return sb.toString();
}
/**
* Create Hashtable representing Contact parameters. This is used to create
* Hessian encoded payload for Contacts upload.
*
* @return Hashtable containing contact parameters.
*/
public Hashtable<String, Object> createHashtable() {
Hashtable<String, Object> htab = new Hashtable<String, Object>();
if (aboutMe != null) {
htab.put(Tags.ABOUT_ME.tag(), aboutMe);
}
if (contactID != null) {
htab.put(Tags.CONTACT_ID.tag(), contactID);
}
if (userID != null) {
htab.put(Tags.USER_ID.tag(), userID);
}
if (updated != null && updated != 0) {
htab.put(Tags.UPDATED.tag(), updated);
}
if (profilePath != null && profilePath.length() > 0) {
htab.put(Tags.PROFILE_PATH.tag(), profilePath);
}
if (friendOfMine != null) {
htab.put(Tags.FRIEND.tag(), friendOfMine);
}
if (deleted != null) {
htab.put(Tags.DELETED.tag(), deleted);
}
if (synctophone != null) {
htab.put(Tags.SYNCTOPHONE.tag(), synctophone);
}
if (groupList != null) {
Vector<Long> vL = new Vector<Long>();
for (Long l : groupList) {
vL.add(l);
}
htab.put(Tags.GROUP_LIST.tag(), vL);
}
if (details != null && details.size() > 0) {
Vector<Object> v = new Vector<Object>();
for (int i = 0; i < details.size(); i++) {
v.add(details.get(i).createHashtable());
}
htab.put(Tags.DETAIL_LIST.tag(), v);
}
return htab;
}
/**
* Create Contact item from Hashtable generated by Hessian-decoder
*
* @param hash Hashtable containing Contact parameters
* @return Contact item created from hashtable or null if hash was corrupted
*/
public static Contact createFromHashtable(Hashtable<String, Object> hash) {
Contact cont = new Contact();
Enumeration<String> e = hash.keys();
while (e.hasMoreElements()) {
String key = e.nextElement();
Object value = hash.get(key);
Tags tag = cont.findTag(key);
cont.setValue(tag, value);
}
return cont;
}
/**
* Sets the value of the member data item associated with the specified tag.
*
* @param tag Current tag
* @param val Value associated with the tag
*/
private void setValue(Tags tag, Object value) {
if (tag == null) {
LogUtils.logE("Contact setValue tag is null");
return;
}
switch (tag) {
case ABOUT_ME:
aboutMe = (String)value;
break;
case CONTACT_ID:
contactID = (Long)value;
break;
case DETAIL_LIST:
@SuppressWarnings("unchecked")
Vector<Hashtable<String, Object>> detailsList = (Vector<Hashtable<String, Object>>)value;
for (Hashtable<String, Object> detailHashtable : detailsList) {
try {
// let's try to create the ContactDetail
// if failing, the detail will just be skipped
final ContactDetail detail = new ContactDetail();
detail.createFromHashtable(detailHashtable);
details.add(detail);
} catch (Exception e) {
LogUtils.logE("Contact.setValue(), the error [" + e + "] occured while adding a detail to the following contact:" + toString());
}
}
break;
case DETAIL:
break;
case GROUP_LIST:
@SuppressWarnings("unchecked")
Vector<Long> gL = (Vector<Long>)value;
groupList = new ArrayList<Long>();
for (Long l : gL) {
groupList.add(l);
}
break;
case UPDATED:
updated = (Long)value;
break;
case USER_ID:
userID = (Long)value;
break;
case SOURCES:
if (sources == null) {
sources = new ArrayList<String>();
}
@SuppressWarnings("unchecked")
Vector<String> vals = (Vector<String>)value;
for (String source : vals) {
sources.add(source);
}
break;
case DELETED:
deleted = (Boolean)value;
break;
case FRIEND:
friendOfMine = (Boolean)value;
break;
case GENDER:
if (gender == null) {
gender = (Integer)value;
}
break;
case SYNCTOPHONE:
synctophone = (Boolean)value;
break;
default:
// Do nothing.
break;
}
}
/**
* Copy parameters from UserProfile object.
*
* @param source UserProfile object to populate Contact with.
*/
public void copy(UserProfile source) {
userID = source.userID;
profilePath = source.profilePath;
contactID = source.contactID;
if (source.sources != null && source.sources.size() > 0) {
if (sources == null) {
sources = new ArrayList<String>();
} else {
sources.clear();
}
sources.addAll(source.sources);
} else {
sources = null;
}
gender = source.gender;
details.clear();
details.addAll(source.details);
aboutMe = source.aboutMe;
friendOfMine = source.friendOfMine;
updated = source.updated;
localContactID = null;
deleted = null;
nativeContactId = null;
groupList = null;
synctophone = null;
}
/**
* Member data definitions used when reading and writing Contact from/to
* Parcels.
*/
private enum MemberData {
LOCALID,
NAME,
ABOUTME,
CONTACTID,
USERID,
SOURCES,
GENDER,
UPDATED,
PROFILEPATH,
FRIENDOFMINE,
DELETED,
NATIVE_CONTACT_ID,
SYNCTOPHONE;
}
/**
* Read Contact from Parcel.
* Note: only called from tests.
*
* @param in Parcel containing Contact item.
*/
public void readFromParcel(Parcel in) {
aboutMe = null;
contactID = null;
userID = null;
sources = null;
gender = null;
updated = null;
profilePath = null;
friendOfMine = null;
deleted = null;
nativeContactId = null;
groupList = null;
synctophone = null;
boolean[] validDataList = new boolean[MemberData.values().length];
in.readBooleanArray(validDataList);
if (validDataList[MemberData.LOCALID.ordinal()]) {
localContactID = in.readLong();
}
if (validDataList[MemberData.ABOUTME.ordinal()]) {
aboutMe = in.readString();
}
if (validDataList[MemberData.CONTACTID.ordinal()]) {
contactID = in.readLong();
}
if (validDataList[MemberData.USERID.ordinal()]) {
userID = in.readLong();
}
if (validDataList[MemberData.SOURCES.ordinal()]) {
sources = new ArrayList<String>();
in.readStringList(sources);
}
if (validDataList[MemberData.GENDER.ordinal()]) {
gender = in.readInt();
}
if (validDataList[MemberData.SYNCTOPHONE.ordinal()]) {
synctophone = (in.readByte() == 0 ? false : true);
}
if (validDataList[MemberData.UPDATED.ordinal()]) {
updated = in.readLong();
}
if (validDataList[MemberData.PROFILEPATH.ordinal()]) {
profilePath = in.readString();
}
if (validDataList[MemberData.FRIENDOFMINE.ordinal()]) {
friendOfMine = (in.readByte() == 0 ? false : true);
}
if (validDataList[MemberData.DELETED.ordinal()]) {
deleted = (in.readByte() == 0 ? false : true);
}
if (validDataList[MemberData.NATIVE_CONTACT_ID.ordinal()]) {
nativeContactId = in.readInt();
}
int noOfDetails = in.readInt();
details.clear();
for (int i = 0; i < noOfDetails; i++) {
ContactDetail detail = ContactDetail.CREATOR.createFromParcel(in);
details.add(detail);
}
}
/** {@inheritDoc} */
@Override
public int describeContents() {
return 1;
}
/** {@inheritDoc}
*
* Note: only called from tests.
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
boolean[] validDataList = new boolean[MemberData.values().length];
int validDataPos = dest.dataPosition();
dest.writeBooleanArray(validDataList); // placeholder for real array
if (localContactID != null) {
validDataList[MemberData.LOCALID.ordinal()] = true;
dest.writeLong(localContactID);
}
if (aboutMe != null) {
validDataList[MemberData.ABOUTME.ordinal()] = true;
dest.writeString(aboutMe);
}
if (contactID != null) {
validDataList[MemberData.CONTACTID.ordinal()] = true;
dest.writeLong(contactID);
}
if (userID != null) {
validDataList[MemberData.USERID.ordinal()] = true;
dest.writeLong(userID);
}
if (sources != null && sources.size() > 0) {
validDataList[MemberData.SOURCES.ordinal()] = true;
dest.writeStringList(sources);
}
if (gender != null) {
validDataList[MemberData.GENDER.ordinal()] = true;
dest.writeInt(gender);
}
if (updated != null) {
validDataList[MemberData.UPDATED.ordinal()] = true;
dest.writeLong(updated);
}
if (profilePath != null) {
validDataList[MemberData.PROFILEPATH.ordinal()] = true;
dest.writeString(profilePath);
}
if (friendOfMine != null) {
validDataList[MemberData.FRIENDOFMINE.ordinal()] = true;
dest.writeByte((byte)(friendOfMine ? 1 : 0));
}
if (deleted != null) {
validDataList[MemberData.DELETED.ordinal()] = true;
dest.writeByte((byte)(deleted ? 1 : 0));
}
if (synctophone != null) {
validDataList[MemberData.SYNCTOPHONE.ordinal()] = true;
dest.writeByte((byte)(synctophone ? 1 : 0));
}
if (nativeContactId != null) {
validDataList[MemberData.NATIVE_CONTACT_ID.ordinal()] = true;
dest.writeInt(nativeContactId);
}
int currentPos = dest.dataPosition();
dest.setDataPosition(validDataPos);
dest.writeBooleanArray(validDataList); // real array
dest.setDataPosition(currentPos);
dest.writeInt(details.size());
for (ContactDetail detail : details) {
detail.writeToParcel(dest, 0);
}
}
/**
* Fetches the preffered ContactDetail for this Contact for example the
* preffered phone number or email address If no such is found, an
* unpreffered contact will be taken
*
* @param detailKey The type of the Detail (PHONE, EMAIL_ADDRESS)
* @return preffered ContactDetail, any ContactDetail if no preferred is
* found or null if no detail at all is found
*/
public ContactDetail getContactDetail(ContactDetail.DetailKeys detailKey) {
ContactDetail preffered = getPrefferedContactDetail(detailKey);
if (preffered != null)
return preffered;
for (ContactDetail detail : details) {
if (detail != null && detail.key == detailKey)
return detail;
}
return null;
}
/**
* Checks if the specified contact detail is available.
* @return <code>true</code> if the contact detail has been found, <code>false</code> otherwise.
*/
public boolean hasContactDetail(ContactDetail.DetailKeys detailKey) {
for (final ContactDetail detail : details) {
if (detail != null && detail.key == detailKey) {
return true;
}
}
return false;
}
/**
* Fetches the preffered ContactDetail for this Contact and a DetailKey for
* example the preffered phone number or email address
*
* @param detailKey The type of the Detail (PHONE, EMAIL_ADDRESS)
* @return preffered ContactDetail or null if no such is found
*/
private ContactDetail getPrefferedContactDetail(ContactDetail.DetailKeys detailKey) {
for (ContactDetail detail : details) {
if (detail != null && detail.key == detailKey
&& detail.order == ContactDetail.ORDER_PREFERRED)
return detail;
}
return null;
}
public String getDetailString() {
StringBuilder sb = new StringBuilder();
for (ContactDetail detail : details) {
sb.append(detail.getValue());
sb.append("|");
}
return sb.toString();
}
}