/* * 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.engine.meprofile; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import android.graphics.BitmapFactory; import com.vodafone360.people.database.DatabaseHelper; import com.vodafone360.people.database.tables.ContactChangeLogTable; import com.vodafone360.people.database.tables.ContactsTable; import com.vodafone360.people.database.tables.StateTable; import com.vodafone360.people.database.tables.ContactChangeLogTable.ContactChangeInfo; import com.vodafone360.people.database.tables.ContactChangeLogTable.ContactChangeType; import com.vodafone360.people.datatypes.Contact; import com.vodafone360.people.datatypes.ContactChanges; import com.vodafone360.people.datatypes.ContactDetail; import com.vodafone360.people.datatypes.UserProfile; import com.vodafone360.people.datatypes.ContactDetail.DetailKeys; import com.vodafone360.people.engine.presence.PresenceDbUtils; import com.vodafone360.people.service.ServiceStatus; import com.vodafone360.people.utils.LogUtils; import com.vodafone360.people.utils.ThirdPartyAccount; import com.vodafone360.people.utils.ThumbnailUtils; /** * This class is a set of utility methods called by * SyncMeEngine to save/read data to/from the database. * */ public class SyncMeDbUtils { /** * Default Me Profile Name. * This is set as an empty string to make it easy to detect when no Name has been set. */ public static final String ME_PROFILE_DEFAULT_NAME = ""; /** * Me profile local contact id. */ private static Long sMeProfileLocalContactId; /** * Mime type for the uploaded thumbnail picture of the me profile. */ private static final String PHOTO_MIME_TYPE = "image/png"; /** * This method create a Me Profile contact in the database. * @param dbHelper DatabaseHelper - the database. * @param meProfile Contact - the Me Profile contact * @return ServiceStatus - ServiceStatus.SUCCESS when the new contact is * successfully created. */ public static ServiceStatus setMeProfile(final DatabaseHelper dbHelper, Contact meProfile) { ServiceStatus status = ServiceStatus.ERROR_DATABASE_CORRUPT; // the contact didn't exist before if (sMeProfileLocalContactId == null) { List<Contact> contactList = new ArrayList<Contact>(); contactList.add(meProfile); status = dbHelper.syncAddContactList(contactList, false, false); if (ServiceStatus.SUCCESS == status) { sMeProfileLocalContactId = meProfile.localContactID; status = StateTable.modifyMeProfileID(sMeProfileLocalContactId, dbHelper.getWritableDatabase()); PresenceDbUtils.resetMeProfileIds(); if (ServiceStatus.SUCCESS != status) { List<ContactsTable.ContactIdInfo> idList = new ArrayList<ContactsTable.ContactIdInfo>(); ContactsTable.ContactIdInfo contactIdInfo = new ContactsTable.ContactIdInfo(); contactIdInfo.localId = meProfile.localContactID; contactIdInfo.serverId = meProfile.contactID; contactIdInfo.nativeId = meProfile.nativeContactId; idList.add(contactIdInfo); dbHelper.syncDeleteContactList(idList, false, false); } } } return status; } /** * This method reads Me Profile contact from the database. * @param dbHelper DatabaseHelper - the database * @param contact Contact - the empty (stub) contact to read into * @return ServiceStatus - ServiceStatus.SUCCESS when the contact is * successfully filled, ServiceStatus.ERROR_NOT_FOUND - if the Me * Profile needs to be created first. */ public static ServiceStatus fetchMeProfile(final DatabaseHelper dbHelper, Contact contact) { if (sMeProfileLocalContactId == null) { return ServiceStatus.ERROR_NOT_FOUND; } return dbHelper.fetchContact(sMeProfileLocalContactId, contact); } /** * This method returns the Me Profile localContactId... * @param dbHelper DatabaseHelper - the database * @return Long - Me Profile localContactId. */ public static Long getMeProfileLocalContactId(DatabaseHelper dbHelper) { if (dbHelper == null) return null; if (sMeProfileLocalContactId == null) { sMeProfileLocalContactId = StateTable.fetchMeProfileId(dbHelper.getReadableDatabase()); } return sMeProfileLocalContactId; } /** * This method sets Me Profile localContactId... * @param meProfileId Long - localContactID */ public static void setMeProfileId(final Long meProfileId) { sMeProfileLocalContactId = meProfileId; } /** * Checks if a given contact id belongs to the Me Contact. * @param dbHelper DatabaseHelper - the database * @param localContactId The contact id to check * @return True if contact being edited is the me profile. */ public static boolean isMeProfile(DatabaseHelper dbHelper, long localContactId) { return Long.valueOf( localContactId).equals(SyncMeDbUtils.getMeProfileLocalContactId(dbHelper)); } /** * This method updates current Me Profile with changes from user profile. * @param dbHelper DatabaseHelper - database * @param currentMeProfile Contact - current me profile, from DB * @param profileChanges - the changes to the current Me Profile * @return String - the profile avatar picture url, null if no picture can * be found. */ public static String updateMeProfile(final DatabaseHelper dbHelper, final Contact currentMeProfile, final UserProfile profileChanges) { if (processMyContactChanges(dbHelper, currentMeProfile, profileChanges) == ServiceStatus.SUCCESS) { return processMyContactDetailsChanges(dbHelper, currentMeProfile, profileChanges); } return null; } /** * This method stores the getMyChanges() response to database - contacts part. * @param dbHelper DatabaseHelper - database. * @param currentMeProfile Contact - me profile contact. * @param profileChanges UserProfile - the contact changes. * @return ServiceStatus - SUCCESS if the contact changes have been successfully processed stored. */ private static ServiceStatus processMyContactChanges(final DatabaseHelper dbHelper, final Contact currentMeProfile, final UserProfile profileChanges) { boolean profileChanged = false; if (profileChanges.userID != null) { currentMeProfile.userID = profileChanges.userID; profileChanged = true; } if (profileChanges.aboutMe != null) { currentMeProfile.aboutMe = profileChanges.aboutMe; profileChanged = true; } if (profileChanges.contactID != null) { currentMeProfile.contactID = profileChanges.contactID; profileChanged = true; } if (profileChanges.gender != null) { currentMeProfile.gender = profileChanges.gender; profileChanged = true; } if (profileChanges.profilePath != null) { currentMeProfile.profilePath = profileChanges.profilePath; profileChanged = true; } if (profileChanges.sources != null) { currentMeProfile.sources.clear(); currentMeProfile.sources.addAll(profileChanges.sources); profileChanged = true; } if (profileChanges.updated != null) { currentMeProfile.updated = profileChanges.updated; profileChanged = true; } if (profileChanged) { ArrayList<Contact> contactList = new ArrayList<Contact>(); contactList.add(currentMeProfile); return dbHelper.syncModifyContactList(contactList, false, false); } return ServiceStatus.SUCCESS; } /** * This method stores the getMyChanges() response to database - details part. * @param dbHelper DatabaseHelper - database. * @param currentMeProfile Contact - me profile contact. * @param profileChanges UserProfile - the contact changes. * @return ServiceStatus - SUCCESS if the contact changes have been successfully processed stored. */ private static String processMyContactDetailsChanges(final DatabaseHelper dbHelper, final Contact currentMeProfile, final UserProfile profileChanges) { String ret = null; final ArrayList<ContactDetail> modifiedDetailList = new ArrayList<ContactDetail>(); final ArrayList<ContactDetail> addedDetailList = new ArrayList<ContactDetail>(); final ArrayList<ContactDetail> deletedDetailList = new ArrayList<ContactDetail>(); for (ContactDetail newDetail : profileChanges.details) { boolean found = false; for (int i = 0; i < currentMeProfile.details.size(); i++) { ContactDetail oldDetail = currentMeProfile.details.get(i); if (DatabaseHelper.doDetailsMatch(newDetail, oldDetail)) { found = true; if (newDetail.deleted != null && newDetail.deleted.booleanValue()) { deletedDetailList.add(oldDetail); } else if (DatabaseHelper.hasDetailChanged(oldDetail, newDetail)) { newDetail.localDetailID = oldDetail.localDetailID; newDetail.localContactID = oldDetail.localContactID; newDetail.nativeContactId = oldDetail.nativeContactId; newDetail.nativeDetailId = oldDetail.nativeDetailId; modifiedDetailList.add(newDetail); if (newDetail.key == DetailKeys.PHOTO) { dbHelper.markMeProfileAvatarChanged(); ret = newDetail.value; } } break; } } // if the detail was not found in the old profile and either there was no deleted flag // in the response or the deleted flag was false we have to add the new detail to the list if ( (!found) && ((null == newDetail.deleted) || (!newDetail.deleted.booleanValue())) ) { newDetail.localContactID = currentMeProfile.localContactID; newDetail.nativeContactId = currentMeProfile.nativeContactId; if (newDetail.key == DetailKeys.PHOTO) { dbHelper.markMeProfileAvatarChanged(); ret = newDetail.value; } addedDetailList.add(newDetail); } } if (!addedDetailList.isEmpty()) { dbHelper.syncAddContactDetailList(addedDetailList, false, false); } if (!modifiedDetailList.isEmpty()) { dbHelper.syncModifyContactDetailList(modifiedDetailList, false, false); } if (!deletedDetailList.isEmpty()) { dbHelper.syncDeleteContactDetailList(deletedDetailList, false, false); } return ret; } /** * The utility method to save Contacts/setMe() response to the database... * @param dbHelper Database - database * @param uploadedMeProfile Contact - me profile which has been uploaded in * Contacts/setMe() call * @param result ContactChanges - the contents of response Contacts/setMe(). * The contact details in response need to be in the same order * as they were in setMe() request */ public static void updateMeProfileDbDetailIds(final DatabaseHelper dbHelper, final ArrayList<ContactDetail> uploadedDetails, final ContactChanges result) { Contact uploadedMeProfile = new Contact(); SyncMeDbUtils.fetchMeProfile(dbHelper, uploadedMeProfile); boolean changed = false; if (result.mUserProfile.userID != null && !result.mUserProfile.userID.equals(uploadedMeProfile.userID)) { uploadedMeProfile.userID = result.mUserProfile.userID; changed = true; } if (result.mUserProfile.contactID != null && !result.mUserProfile.contactID.equals(uploadedMeProfile.contactID)) { uploadedMeProfile.contactID = result.mUserProfile.contactID; changed = true; } if (changed) { dbHelper.modifyContactServerId(uploadedMeProfile.localContactID, uploadedMeProfile.contactID, uploadedMeProfile.userID); } ListIterator<ContactDetail> destIt = uploadedDetails.listIterator(); for (ContactDetail srcDetail : result.mUserProfile.details) { if (!destIt.hasNext()) { LogUtils .logE("SyncMeDbUtils updateProfileDbDetailsId() - # of details in response > # in request"); return; } final ContactDetail destDetail = destIt.next(); if (srcDetail.key == null || !srcDetail.key.equals(destDetail.key)) { LogUtils.logE("SyncMeDbUtils updateProfileDbDetailsId() - details order is wrong"); break; } destDetail.unique_id = srcDetail.unique_id; dbHelper.syncContactDetail(destDetail.localDetailID, destDetail.unique_id); } } /** * The utility method to save the status text change to the database... * @param dbHelper DatabaseHelper - database * @param statusText String - status text * @return ContactDetail - the modified or created ContactDetail with key * ContactDetail.DetailKeys.PRESENCE_TEXT */ public static ContactDetail updateStatus(final DatabaseHelper dbHelper, final String statusText) { Contact meContact = new Contact(); if (fetchMeProfile(dbHelper, meContact) == ServiceStatus.SUCCESS) { // try to modify an existing detail for (ContactDetail detail : meContact.details) { if (detail.key == ContactDetail.DetailKeys.PRESENCE_TEXT) { detail.value = statusText; // Currently it's only possible to post a status on // Vodafone sns detail.alt = ThirdPartyAccount.SNS_TYPE_VODAFONE; if (ServiceStatus.SUCCESS == dbHelper.modifyContactDetail(detail)) { return detail; } } } // create a new detail instead ContactDetail contactDetail = new ContactDetail(); contactDetail.setValue(statusText, ContactDetail.DetailKeys.PRESENCE_TEXT, null); contactDetail.alt = ThirdPartyAccount.SNS_TYPE_VODAFONE; contactDetail.localContactID = meContact.localContactID; if (ServiceStatus.SUCCESS == dbHelper.addContactDetail(contactDetail)) { return contactDetail; } } return null; } /** * The utility method to save Contacts/setMe() response for the status text * change to the database... * @param dbHelper DatabaseHelper - database. * @param ContactChanges result - status text change. */ public static void savePresenceStatusResponse(final DatabaseHelper dbHelper, ContactChanges result) { Contact currentMeProfile = new Contact(); if (ServiceStatus.SUCCESS == SyncMeDbUtils.fetchMeProfile(dbHelper, currentMeProfile)) { boolean changed = false; if (result.mUserProfile.userID != null && (!result.mUserProfile.userID.equals(currentMeProfile.userID))) { currentMeProfile.userID = result.mUserProfile.userID; changed = true; } if (result.mUserProfile.contactID != null && (!result.mUserProfile.contactID.equals(currentMeProfile.contactID))) { currentMeProfile.contactID = result.mUserProfile.contactID; changed = true; } if (changed) { dbHelper.modifyContactServerId(currentMeProfile.localContactID, currentMeProfile.contactID, currentMeProfile.userID); } for (ContactDetail oldStatus : currentMeProfile.details) { if (oldStatus.key == ContactDetail.DetailKeys.PRESENCE_TEXT) { for (ContactDetail newStatus : result.mUserProfile.details) { if (newStatus.key == ContactDetail.DetailKeys.PRESENCE_TEXT) { oldStatus.unique_id = newStatus.unique_id; dbHelper.syncContactDetail(oldStatus.localDetailID, oldStatus.unique_id); break; } } } } } } /** * A utility method to save the Me Profile contact before sending the * updates to backend * * @param dbHelper DataBaseHelper - database * @param meProfile - the new me Profile to push to server * @return - ArrayList of ContactDetails to be pushed to server */ public static ArrayList<ContactDetail> saveContactDetailChanges(final DatabaseHelper dbHelper, final Contact meProfile) { ArrayList<ContactDetail> updates = new ArrayList<ContactDetail>(); populateWithModifiedDetails(dbHelper, updates, meProfile); // add the deleted details from the change log table to the contact // details populateWithDeletedContactDetails(dbHelper, updates, meProfile.contactID); return updates; } private static void populateWithModifiedDetails(final DatabaseHelper dbHelper, final ArrayList<ContactDetail> updates, Contact meProfile) { boolean avatarChanged = dbHelper.isMeProfileAvatarChanged(); for (ContactDetail detail : meProfile.details) { // LogUtils.logV("meProfile.details:" + detail); if (avatarChanged && detail.key == ContactDetail.DetailKeys.PHOTO) { populatePhotoDetail(dbHelper, meProfile, detail); updates.add(detail); } else if (detail.key != ContactDetail.DetailKeys.VCARD_INTERNET_ADDRESS && detail.key != ContactDetail.DetailKeys.VCARD_IMADDRESS && detail.key != ContactDetail.DetailKeys.PRESENCE_TEXT) { // fix for bug 16029 - it's a server issue (getMe() returns // broken details) but the workaround on the client side is // just // not to add the extra details to setMe() request detail.updated = null; // LogUtils.logV("meProfile.details: put"); updates.add(detail); } } } /** * This method reads a photo data from a file into the ContactDetail... * @param dbHelper DatabaseHelper - database * @param meProfile Contact - me profile contact * @param detail ContactDetail - the detail to write the photo into. */ private static void populatePhotoDetail(final DatabaseHelper dbHelper, final Contact meProfile, final ContactDetail detail) { String path = ThumbnailUtils.thumbnailPath(meProfile.localContactID); detail.photo = BitmapFactory.decodeFile(path); if (detail.photo == null) { LogUtils.logE("SyncMeDbUtils saveContactDetailChanges: " + "Unable to decode avatar"); } detail.photo_mime_type = PHOTO_MIME_TYPE; // when sending the "bytes" the "val" (photoDetail.value)has to // be null, otherwise the the picture on the website is not // updated detail.value = null; detail.updated = null; detail.order = 0; } /** * This method adds the deleted details to the detail list sent to server... * @param dbHelper DatabaseHelper - database * @param contactDetails List<ContactDetail> - the deleted details list * @param contactId Long - Me Profile local contact id. */ private static void populateWithDeletedContactDetails(final DatabaseHelper dbHelper, final List<ContactDetail> contactDetails, final Long contactId) { List<ContactChangeInfo> deletedDetails = new ArrayList<ContactChangeInfo>(); if (!ContactChangeLogTable.fetchMeProfileChangeLog(deletedDetails, ContactChangeType.DELETE_DETAIL, dbHelper.getReadableDatabase(), contactId)) { LogUtils.logE("UploadServerContacts populateWithDeletedContactDetails -" + " Unable to fetch contact changes from database"); return; } for (int i = 0; i < deletedDetails.size(); i++) { ContactChangeInfo info = deletedDetails.get(i); final ContactDetail detail = new ContactDetail(); detail.localDetailID = info.mLocalDetailId; detail.key = info.mServerDetailKey; detail.unique_id = info.mServerDetailId; detail.deleted = true; contactDetails.add(detail); } dbHelper.deleteContactChanges(deletedDetails); } }