/* * 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(); } }