/******************************************************************************* * Software Name : RCS IMS Stack * * Copyright (C) 2010 France Telecom S.A. * * 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.orangelabs.rcs.provider.eab; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import android.accounts.AccountManager; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.os.RemoteException; import android.provider.ContactsContract; import android.provider.ContactsContract.AggregationExceptions; import android.provider.ContactsContract.CommonDataKinds.Im; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.Photo; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.Website; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.StatusUpdates; import com.orangelabs.rcs.R; import com.orangelabs.rcs.addressbook.AuthenticationService; import com.orangelabs.rcs.provider.settings.RcsSettings; import com.orangelabs.rcs.service.api.client.capability.Capabilities; import com.orangelabs.rcs.service.api.client.contacts.ContactInfo; import com.orangelabs.rcs.service.api.client.presence.FavoriteLink; import com.orangelabs.rcs.service.api.client.presence.Geoloc; import com.orangelabs.rcs.service.api.client.presence.PhotoIcon; import com.orangelabs.rcs.service.api.client.presence.PresenceInfo; import com.orangelabs.rcs.utils.CloseableUtils; import com.orangelabs.rcs.utils.PhoneUtils; import com.orangelabs.rcs.utils.logger.Logger; /** * Contains utility methods for interfacing with the Android SDK ContactsProvider. * * @author jexa7410 * @author Deutsche Telekom AG */ public final class ContactsManager { /** * Current instance */ private static ContactsManager instance = null; /** * Context */ private Context ctx; /** * Constant for invalid id. */ private static final int INVALID_ID = -1; /** * MIME type for contact number */ private static final String MIMETYPE_NUMBER = "vnd.android.cursor.item/com.orangelabs.rcs.number"; /** * MIME type for RCS status */ private static final String MIMETYPE_RCS_STATUS = "vnd.android.cursor.item/com.orangelabs.rcs.rcs-status"; /** * MIME type for RCS registration state */ private static final String MIMETYPE_REGISTRATION_STATE = "vnd.android.cursor.item/com.orangelabs.rcs.registration-state"; /** * MIME type for RCS status timestamp */ private static final String MIMETYPE_RCS_STATUS_TIMESTAMP = "vnd.android.cursor.item/com.orangelabs.rcs.rcs-status.timestamp"; /** * MIME type for presence status */ private static final String MIMETYPE_PRESENCE_STATUS = "vnd.android.cursor.item/com.orangelabs.rcs.presence-status"; /** * MIME type for free text */ private static final String MIMETYPE_FREE_TEXT = "vnd.android.cursor.item/com.orangelabs.rcs.free-text"; /** * MIME type for web link */ private static final String MIMETYPE_WEBLINK = "vnd.android.cursor.item/com.orangelabs.rcs.weblink"; /** * MIME type for photo icon */ private static final String MIMETYPE_PHOTO = ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE; /** * MIME type for photo icon etag */ private static final String MIMETYPE_PHOTO_ETAG = "vnd.android.cursor.item/com.orangelabs.rcs.photo-etag"; /** * MIME type for presence timestamp */ private static final String MIMETYPE_PRESENCE_TIMESTAMP = "vnd.android.cursor.item/com.orangelabs.rcs.presence.timestamp"; /** * MIME type for capability timestamp */ private static final String MIMETYPE_CAPABILITY_TIMESTAMP = "vnd.android.cursor.item/com.orangelabs.rcs.capability.timestamp"; /** * MIME type for CS_VIDEO capability */ private static final String MIMETYPE_CAPABILITY_CS_VIDEO = "vnd.android.cursor.item/com.orangelabs.rcs.capability.cs-video"; /** * MIME type for GSMA_CS_IMAGE (image sharing) capability */ private static final String MIMETYPE_CAPABILITY_IMAGE_SHARING = "vnd.android.cursor.item/com.orangelabs.rcs.capability.image-sharing"; /** * MIME type for 3GPP_CS_VOICE (video sharing) capability */ private static final String MIMETYPE_CAPABILITY_VIDEO_SHARING = "vnd.android.cursor.item/com.orangelabs.rcs.capability.video-sharing"; /** * MIME type for RCS IP Voice Call capability */ private static final String MIMETYPE_CAPABILITY_IP_VOICE_CALL = "vnd.android.cursor.item/com.orangelabs.rcs.capability.ip-voice-call"; /** * MIME type for RCS IP Video Call capability */ private static final String MIMETYPE_CAPABILITY_IP_VIDEO_CALL = "vnd.android.cursor.item/com.orangelabs.rcs.capability.ip-video-call"; /** * MIME type for RCS_IM (IM session) capability */ private static final String MIMETYPE_CAPABILITY_IM_SESSION = "vnd.android.cursor.item/com.orangelabs.rcs.capability.im-session"; /** * MIME type for RCS_FT (file transfer) capability */ private static final String MIMETYPE_CAPABILITY_FILE_TRANSFER = "vnd.android.cursor.item/com.orangelabs.rcs.capability.file-transfer"; /** * MIME type for presence discovery capability */ private static final String MIMETYPE_CAPABILITY_PRESENCE_DISCOVERY = "vnd.android.cursor.item/com.orangelabs.rcs.capability.presence-discovery"; /** * MIME type for social presence capability */ private static final String MIMETYPE_CAPABILITY_SOCIAL_PRESENCE = "vnd.android.cursor.item/com.orangelabs.rcs.capability.social-presence"; /** * MIME type for social presence capability */ private static final String MIMETYPE_CAPABILITY_GEOLOCATION_PUSH = "vnd.android.cursor.item/com.orangelabs.rcs.capability.geolocation-push"; /** * MIME type for file transfer thumbnail capability */ private static final String MIMETYPE_CAPABILITY_FILE_TRANSFER_THUMBNAIL = "vnd.android.cursor.item/com.orangelabs.rcs.capability.file-transfer-thumbnail"; /** * MIME type for file transfer over HTTP capability */ private static final String MIMETYPE_CAPABILITY_FILE_TRANSFER_HTTP = "vnd.android.cursor.item/com.orangelabs.rcs.capability.file-transfer-http"; /** * MIME type for file transfer S&F capability */ private static final String MIMETYPE_CAPABILITY_FILE_TRANSFER_SF = "vnd.android.cursor.item/com.orangelabs.rcs.capability.file-transfer-sf"; /** * MIME type for group chat S&F capability */ private static final String MIMETYPE_CAPABILITY_GROUP_CHAT_SF = "vnd.android.cursor.item/com.orangelabs.rcs.capability.group-chat-sf"; /** * MIME type for RCS extensions */ private static final String MIMETYPE_CAPABILITY_EXTENSIONS = "vnd.android.cursor.item/com.orangelabs.rcs.capability.extensions"; /** * MIME type when RCS extensions that I also support are present */ private static final String MIMETYPE_CAPABILITY_COMMON_EXTENSION = "vnd.android.cursor.item/com.orangelabs.rcs.capability.support.extension"; /** * MIME type for seeing my profile */ private static final String MIMETYPE_SEE_MY_PROFILE = "vnd.android.cursor.item/com.orangelabs.rcs.my-profile"; /** * MIME type for a RCS contact */ private static final String MIMETYPE_RCS_CONTACT = "vnd.android.cursor.item/com.orangelabs.rcs.rcs-contact"; /** * MIME type for a RCS capable contact */ private static final String MIMETYPE_RCS_CAPABLE_CONTACT = "vnd.android.cursor.item/com.orangelabs.rcs.rcs-capable-contact"; /** * MIME type for a non RCS contact */ private static final String MIMETYPE_NOT_RCS_CONTACT = "vnd.android.cursor.item/com.orangelabs.rcs.not-rcs-contact"; /** * MIME type for block IM status */ private static final String MIMETYPE_IM_BLOCKED = "vnd.android.cursor.item/com.orangelabs.rcs.im-blocked"; /** * MIME type for block FT status */ private static final String MIMETYPE_FT_BLOCKED = "vnd.android.cursor.item/com.orangelabs.rcs.ft-blocked"; /** * ONLINE available status */ private static final int PRESENCE_STATUS_ONLINE = 5; //StatusUpdates.AVAILABLE; /** * OFFLINE available status */ private static final int PRESENCE_STATUS_OFFLINE = 0; //StatusUpdates.OFFLINE; /** * NOT SET available status */ private static final int PRESENCE_STATUS_NOT_SET = 1; //StatusUpdates.INVISIBLE; /** * Account name for SIM contacts */ private static final String SIM_ACCOUNT_NAME = "com.android.contacts.sim"; /** * Contact for "Me" */ private static final String MYSELF = "myself"; /** * The logger */ private Logger logger = Logger.getLogger(getClass().getName()); /** * Create instance * * @param ctx Context */ public static synchronized void createInstance(Context ctx) { if (instance == null) { instance = new ContactsManager(ctx); } } /** * Returns instance * * @return Instance */ public static ContactsManager getInstance() { return instance; } /** * Constructor * * @param ctx Application context */ private ContactsManager(Context ctx) { this.ctx = ctx; } /** * Set my presence info in the EAB * * @param info Presence info * @throws ContactsManagerException */ public void setMyInfo(PresenceInfo newPresenceInfo) throws ContactsManagerException { if (logger.isActivated()) { logger.info("Set my presence info"); } if (!RcsSettings.getInstance().isSocialPresenceSupported()){ return; } long myRawContactId = getRawContactIdForMe(); PresenceInfo oldPresenceInfo = getMyPresenceInfo(); ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); // Modify capability timestamp ContentProviderOperation op = modifyCapabilityTimestampForContact(myRawContactId, MYSELF, newPresenceInfo.getTimestamp()); if (op!=null){ ops.add(op); } // Set the new availability status int newAvailability = ContactInfo.REGISTRATION_STATUS_UNKNOWN; if (newPresenceInfo.isOnline()){ newAvailability = ContactInfo.REGISTRATION_STATUS_ONLINE; }else if (newPresenceInfo.isOffline()){ newAvailability = ContactInfo.REGISTRATION_STATUS_OFFLINE; } // Set the old availability status int oldAvailability = ContactInfo.REGISTRATION_STATUS_UNKNOWN; if (oldPresenceInfo.isOnline()){ oldAvailability = ContactInfo.REGISTRATION_STATUS_ONLINE; }else if (oldPresenceInfo.isOffline()){ oldAvailability = ContactInfo.REGISTRATION_STATUS_OFFLINE; } // Modify presence List<ContentProviderOperation> presenceOps = modifyPresenceForContact(myRawContactId, MYSELF, newPresenceInfo, oldPresenceInfo); for (int i=0;i<presenceOps.size();i++){ op = presenceOps.get(i); if (op!=null){ ops.add(op); } } // Modify contact registration state (for native address book) List<ContentProviderOperation> registrationOps = modifyContactRegistrationState(myRawContactId, MYSELF, newAvailability, oldAvailability, newPresenceInfo.getFreetext(), oldPresenceInfo.getFreetext()); for (int i=0;i<registrationOps.size();i++){ op = registrationOps.get(i); if (op!=null){ ops.add(op); } } if (!ops.isEmpty()){ // Do the actual database modifications try { ctx.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (RemoteException e) { if (logger.isActivated()){ logger.error("Something went wrong when updating the database with the contact info",e); } throw new ContactsManagerException(e.getMessage()); } catch (OperationApplicationException e) { if (logger.isActivated()){ logger.error("Something went wrong when updating the database with the contact info",e); } throw new ContactsManagerException(e.getMessage()); } } } /** * Set my photo-icon in the EAB * * @param photo Photo * @throws ContactsManagerException */ public void setMyPhotoIcon(PhotoIcon photo) throws ContactsManagerException { if (logger.isActivated()) { logger.info("Set my photo-icon"); } if (!RcsSettings.getInstance().isSocialPresenceSupported()){ return; } try { setContactPhotoIcon(MYSELF, photo); } catch (Exception e) { if (logger.isActivated()) { logger.error("Internal exception", e); } throw new ContactsManagerException(e.getMessage()); } } /** * Remove my photo-icon in the EAB * * @throws ContactsManagerException */ public void removeMyPhotoIcon() throws ContactsManagerException { if (logger.isActivated()) { logger.info("Remove my photo-icon"); } if (!RcsSettings.getInstance().isSocialPresenceSupported()){ return; } try { setContactPhotoIcon(MYSELF, null); } catch (Exception e) { if (logger.isActivated()) { logger.error("Internal exception", e); } throw new ContactsManagerException(e.getMessage()); } } /** * Returns my presence info from the EAB * * @return Presence info or null in case of error */ public PresenceInfo getMyPresenceInfo() { if (logger.isActivated()) { logger.info("Get my presence info"); } if (!RcsSettings.getInstance().isSocialPresenceSupported()){ return new PresenceInfo(); } long rawContactId = getRawContactIdForMe(); Cursor cursor = getRawContactDataCursor(rawContactId); return getContactInfoFromCursor(cursor).getPresenceInfo(); } /** * Return the row id of a profile number in the EAB * * @param number Profile number * @return Row id */ private int getProfileRowId(String number) { int rowId = -1; try { String where = RichAddressBookData.KEY_CONTACT_NUMBER + " = " + "\"" + number + "\""; Cursor cur = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, null, where, null, null); // Changed by Deutsche Telekom if (cur != null) { if (cur.moveToFirst()) { rowId = cur.getInt(cur.getColumnIndex(RichAddressBookData.KEY_ID)); } cur.close(); } } catch (Exception e) { if (logger.isActivated()) { logger.error("Internal exception", e); } } return rowId; } /** * Set the info of a contact * * @param newInfo New contact info * @param oldInfo Old contact info * @throws ContactsManagerException */ public void setContactInfo(ContactInfo newInfo, ContactInfo oldInfo) throws ContactsManagerException { if (logger.isActivated()) { logger.info("Set contact info for " + newInfo.getContact()); } // May be called from outside the core, so be sure the number format is international before doing the queries String contact = PhoneUtils.extractNumberFromUri(newInfo.getContact()); // Check if we have an entry for the contact boolean hasEntryInRichAddressBook = false; Cursor cur = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, new String[]{RichAddressBookData.KEY_CONTACT_NUMBER}, RichAddressBookData.KEY_CONTACT_NUMBER +"=?", new String[]{contact}, null); // Changed by Deutsche Telekom if (cur != null) { if (cur.moveToFirst()) { hasEntryInRichAddressBook = true; } cur.close(); } ContentValues values = new ContentValues(); values.put(RichAddressBookData.KEY_CONTACT_NUMBER, contact); // Save RCS status values.put(RichAddressBookData.KEY_RCS_STATUS, newInfo.getRcsStatus()); values.put(RichAddressBookData.KEY_RCS_STATUS_TIMESTAMP, newInfo.getRcsStatusTimestamp()); // Save capabilities, if the contact is not registered, do not set the capability to true boolean isRegistered = (newInfo.getRegistrationState() == ContactInfo.REGISTRATION_STATUS_ONLINE); Capabilities newCapabilities = newInfo.getCapabilities(); values.put(RichAddressBookData.KEY_CAPABILITY_CS_VIDEO, Boolean.toString(newCapabilities.isCsVideoSupported() && isRegistered)); values.put(RichAddressBookData.KEY_CAPABILITY_FILE_TRANSFER, Boolean.toString((newCapabilities.isFileTransferSupported() && isRegistered) || (RcsSettings.getInstance().isFtAlwaysOn() && newInfo.isRcsContact()))); values.put(RichAddressBookData.KEY_CAPABILITY_IMAGE_SHARING, Boolean.toString(newCapabilities.isImageSharingSupported() && isRegistered)); values.put(RichAddressBookData.KEY_CAPABILITY_IM_SESSION, Boolean.toString((newCapabilities.isImSessionSupported() && isRegistered)|| (RcsSettings.getInstance().isImAlwaysOn() && newInfo.isRcsContact()))); values.put(RichAddressBookData.KEY_CAPABILITY_PRESENCE_DISCOVERY, Boolean.toString(newCapabilities.isPresenceDiscoverySupported() && isRegistered)); values.put(RichAddressBookData.KEY_CAPABILITY_SOCIAL_PRESENCE, Boolean.toString(newCapabilities.isSocialPresenceSupported() && isRegistered)); values.put(RichAddressBookData.KEY_CAPABILITY_VIDEO_SHARING, Boolean.toString(newCapabilities.isVideoSharingSupported() && isRegistered)); values.put(RichAddressBookData.KEY_CAPABILITY_GEOLOCATION_PUSH, Boolean.toString(newCapabilities.isGeolocationPushSupported() && isRegistered)); values.put(RichAddressBookData.KEY_CAPABILITY_IP_VOICE_CALL, Boolean.toString(newCapabilities.isIPVoiceCallSupported() && isRegistered)); values.put(RichAddressBookData.KEY_CAPABILITY_IP_VIDEO_CALL, Boolean.toString(newCapabilities.isIPVideoCallSupported() && isRegistered)); final boolean isFileTransferHttpAllowed = (isRegistered || (RcsSettings.getInstance().isImAlwaysOn())); values.put(RichAddressBookData.KEY_CAPABILITY_FILE_TRANSFER_HTTP, Boolean.toString(newCapabilities.isFileTransferHttpSupported() && isFileTransferHttpAllowed)); values.put(RichAddressBookData.KEY_CAPABILITY_FILE_TRANSFER_THUMBNAIL, Boolean.toString(newCapabilities.isFileTransferThumbnailSupported() && isRegistered)); values.put(RichAddressBookData.KEY_CAPABILITY_FILE_TRANSFER_SF, Boolean.toString(newCapabilities.isFileTransferStoreForwardSupported() && isRegistered)); values.put(RichAddressBookData.KEY_CAPABILITY_GROUP_CHAT_SF, Boolean.toString(newCapabilities.isGroupChatStoreForwardSupported() && isRegistered)); // Save the capabilities extensions ArrayList<String> newExtensions = newCapabilities.getSupportedExtensions(); StringBuffer aggregatedExtensions = new StringBuffer(); for (int i=0; i<newExtensions.size(); i++){ aggregatedExtensions.append(newExtensions.get(i)+";"); } values.put(RichAddressBookData.KEY_CAPABILITY_EXTENSIONS, aggregatedExtensions.toString()); // Save capabilities timestamp values.put(RichAddressBookData.KEY_CAPABILITY_TIMESTAMP, newCapabilities.getTimestamp()); // Save presence infos PresenceInfo newPresenceInfo = newInfo.getPresenceInfo(); values.put(RichAddressBookData.KEY_PRESENCE_SHARING_STATUS, newPresenceInfo.getPresenceStatus()); values.put(RichAddressBookData.KEY_PRESENCE_FREE_TEXT, newPresenceInfo.getFreetext()); FavoriteLink favLink = newPresenceInfo.getFavoriteLink(); if (favLink == null) { values.put(RichAddressBookData.KEY_PRESENCE_WEBLINK_NAME, ""); values.put(RichAddressBookData.KEY_PRESENCE_WEBLINK_URL, ""); } else { values.put(RichAddressBookData.KEY_PRESENCE_WEBLINK_NAME, favLink.getName()); values.put(RichAddressBookData.KEY_PRESENCE_WEBLINK_URL, favLink.getLink()); } Geoloc geoloc = newPresenceInfo.getGeoloc(); if (geoloc == null) { values.put(RichAddressBookData.KEY_PRESENCE_GEOLOC_EXIST_FLAG, RichAddressBookData.FALSE_VALUE); values.put(RichAddressBookData.KEY_PRESENCE_GEOLOC_LATITUDE, 0); values.put(RichAddressBookData.KEY_PRESENCE_GEOLOC_LONGITUDE, 0); values.put(RichAddressBookData.KEY_PRESENCE_GEOLOC_ALTITUDE, 0); } else { values.put(RichAddressBookData.KEY_PRESENCE_GEOLOC_EXIST_FLAG, RichAddressBookData.TRUE_VALUE); values.put(RichAddressBookData.KEY_PRESENCE_GEOLOC_LATITUDE, geoloc.getLatitude()); values.put(RichAddressBookData.KEY_PRESENCE_GEOLOC_LONGITUDE, geoloc.getLongitude()); values.put(RichAddressBookData.KEY_PRESENCE_GEOLOC_ALTITUDE, geoloc.getAltitude()); } values.put(RichAddressBookData.KEY_PRESENCE_TIMESTAMP, newPresenceInfo.getTimestamp()); PhotoIcon photoIcon = newPresenceInfo.getPhotoIcon(); if (photoIcon == null) { values.put(RichAddressBookData.KEY_PRESENCE_PHOTO_ETAG, ""); values.put(RichAddressBookData.KEY_PRESENCE_PHOTO_EXIST_FLAG, RichAddressBookData.FALSE_VALUE); } else { if (photoIcon.getContent() != null) { values.put(RichAddressBookData.KEY_PRESENCE_PHOTO_EXIST_FLAG, RichAddressBookData.TRUE_VALUE); } else { values.put(RichAddressBookData.KEY_PRESENCE_PHOTO_EXIST_FLAG, RichAddressBookData.FALSE_VALUE); } values.put(RichAddressBookData.KEY_PRESENCE_PHOTO_ETAG, photoIcon.getEtag()); } // Save registration state values.put(RichAddressBookData.KEY_REGISTRATION_STATE, newInfo.getRegistrationState()); if (hasEntryInRichAddressBook) { // Update ctx.getContentResolver().update(RichAddressBookData.CONTENT_URI, values, RichAddressBookData.KEY_CONTACT_NUMBER + "=?", new String[] { contact }); } else { // Insert ctx.getContentResolver().insert(RichAddressBookData.CONTENT_URI, values); } // Save presence photo content if (photoIcon != null) { byte photoContent[] = photoIcon.getContent(); if (photoContent != null) { int rowId = getProfileRowId(contact); Uri photoUri = ContentUris.withAppendedId(RichAddressBookData.CONTENT_URI, rowId); OutputStream outstream = null; try { outstream = ctx.getContentResolver().openOutputStream(photoUri); outstream.write(photoContent); outstream.flush(); } catch (FileNotFoundException e) { if (logger.isActivated()){ logger.error("Photo can't be saved",e); } } catch (IOException e) { if (logger.isActivated()){ logger.error("Photo can't be saved",e); } } finally { CloseableUtils.close(outstream); } } } // Get all the Ids from raw contacts that have this phone number List<Long> rawContactIds = getRawContactIdsFromPhoneNumber(contact); if (rawContactIds.isEmpty()) { // If the number is not in the native address book, we are done. return; } // For each, prepare the modifications ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); for (int i = 0; i < rawContactIds.size(); i++) { long rawContactId = rawContactIds.get(i); // Get the associated RCS raw contact id long rcsRawContactId = getAssociatedRcsRawContact(rawContactId, contact); if (!newInfo.isRcsContact()) { // If the contact is not a RCS contact anymore, we have to delete the corresponding native raw contacts ops.add(ContentProviderOperation.newDelete(RawContacts.CONTENT_URI) .withSelection(RawContacts._ID + "=?", new String[]{Long.toString(rcsRawContactId)}) .build()); // Also delete the corresponding entries in the aggregation provider ctx.getContentResolver().delete(AggregationData.CONTENT_URI, AggregationData.KEY_RCS_RAW_CONTACT_ID + "=?", new String[]{Long.toString(rcsRawContactId)}); } else { // If the contact is still a RCS contact, we have to update the native raw contacts if (rcsRawContactId == INVALID_ID) { // If no RCS raw contact id is associated to the raw contact, create one with the right infos rcsRawContactId = createRcsContact(newInfo, rawContactId); // Nothing to modify, as the new contact will have taken the new infos continue; } // Modify the contact type List<ContentProviderOperation> contactTypeOps = modifyContactTypeForContact(rcsRawContactId, contact, newInfo.getRcsStatus(), oldInfo.getRcsStatus()); for (int j = 0; j < contactTypeOps.size(); j++) { ContentProviderOperation op = contactTypeOps.get(j); if (op!=null){ ops.add(op); } } // Modify the capabilities // If the contact is not registered, do not set the capability to true // Cs Video ContentProviderOperation op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_CS_VIDEO, newInfo.getCapabilities().isCsVideoSupported()&& isRegistered, oldInfo.getCapabilities().isCsVideoSupported()); if (op!=null){ ops.add(op); } // File transfer op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_FILE_TRANSFER, newInfo.getCapabilities().isFileTransferSupported() && isRegistered, oldInfo.getCapabilities().isFileTransferSupported()); if (op!=null){ ops.add(op); } // Image sharing op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_IMAGE_SHARING, newInfo.getCapabilities().isImageSharingSupported() && isRegistered, oldInfo.getCapabilities().isImageSharingSupported()); if (op!=null){ ops.add(op); } // IM session // For IM, also check if the IM capability always on is activated, for RCS contacts op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_IM_SESSION, (newInfo.getCapabilities().isImSessionSupported() && isRegistered)||(RcsSettings.getInstance().isImAlwaysOn() && newInfo.isRcsContact()), oldInfo.getCapabilities().isImSessionSupported()); if (op!=null){ ops.add(op); } // Video sharing op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_VIDEO_SHARING, newInfo.getCapabilities().isVideoSharingSupported() && isRegistered, oldInfo.getCapabilities().isVideoSharingSupported()); if (op!=null){ ops.add(op); } // IP Voice call op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_IP_VOICE_CALL, newInfo.getCapabilities().isIPVoiceCallSupported() && isRegistered, oldInfo.getCapabilities().isIPVoiceCallSupported()); if (op!=null){ ops.add(op); } // IP video call op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_IP_VIDEO_CALL, newInfo.getCapabilities().isIPVideoCallSupported() && isRegistered, oldInfo.getCapabilities().isIPVideoCallSupported()); if (op!=null){ ops.add(op); } // Presence discovery op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_PRESENCE_DISCOVERY, newInfo.getCapabilities().isPresenceDiscoverySupported() && isRegistered, oldInfo.getCapabilities().isPresenceDiscoverySupported()); if (op!=null){ ops.add(op); } // Social presence op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_SOCIAL_PRESENCE, newInfo.getCapabilities().isSocialPresenceSupported() && isRegistered, oldInfo.getCapabilities().isSocialPresenceSupported()); if (op!=null){ ops.add(op); } // Geolocation push op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_GEOLOCATION_PUSH, newInfo.getCapabilities().isGeolocationPushSupported() && isRegistered, oldInfo.getCapabilities().isGeolocationPushSupported()); if (op!=null){ ops.add(op); } // File transfer thumbnail op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_FILE_TRANSFER_THUMBNAIL, newInfo.getCapabilities().isFileTransferThumbnailSupported() && isRegistered, oldInfo.getCapabilities().isFileTransferThumbnailSupported()); if (op!=null){ ops.add(op); } // File transfer HTTP op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_FILE_TRANSFER_HTTP, newInfo.getCapabilities().isFileTransferHttpSupported() && (isRegistered || (RcsSettings.getInstance().isImAlwaysOn())), oldInfo.getCapabilities().isFileTransferHttpSupported()); if (op!=null){ ops.add(op); } // File transfer S&F op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_FILE_TRANSFER_SF, newInfo.getCapabilities().isFileTransferStoreForwardSupported() && isRegistered, oldInfo.getCapabilities().isFileTransferStoreForwardSupported()); if (op!=null){ ops.add(op); } // Group chat S&F op = modifyMimeTypeForContact(rcsRawContactId, contact, MIMETYPE_CAPABILITY_GROUP_CHAT_SF, newInfo.getCapabilities().isGroupChatStoreForwardSupported() && isRegistered, oldInfo.getCapabilities().isGroupChatStoreForwardSupported()); if (op!=null){ ops.add(op); } // RCS extensions ArrayList<String> extensions = newInfo.getCapabilities().getSupportedExtensions(); if (!isRegistered){ // If contact is not registered, do not put any extensions extensions.clear(); } List<ContentProviderOperation> extensionOps = modifyExtensionsCapabilityForContact(rcsRawContactId, contact, extensions, oldInfo.getCapabilities().getSupportedExtensions()); for (int j=0;j<extensionOps.size();j++){ op = extensionOps.get(j); if (op!=null){ ops.add(op); } } // Contact capabilities timestamp op = modifyCapabilityTimestampForContact(rcsRawContactId, contact, newInfo.getCapabilities().getTimestamp()); if (op!=null){ ops.add(op); } // New contact registration state String newFreeText = ""; if (newInfo.getPresenceInfo()!=null){ newFreeText = newInfo.getPresenceInfo().getFreetext(); } // Old contact registration state String oldFreeText = ""; if (oldInfo.getPresenceInfo()!=null){ oldFreeText = oldInfo.getPresenceInfo().getFreetext(); } List<ContentProviderOperation> registrationOps = modifyContactRegistrationState(rcsRawContactId, contact, newInfo.getRegistrationState(), oldInfo.getRegistrationState(), newFreeText, oldFreeText); for (int j=0;j<registrationOps.size();j++){ op = registrationOps.get(j); if (op!=null){ ops.add(op); } } // Presence fields List<ContentProviderOperation> presenceOps = modifyPresenceForContact(rcsRawContactId, contact, newInfo.getPresenceInfo(), oldInfo.getPresenceInfo()); for (int j=0;j<presenceOps.size();j++){ op = presenceOps.get(j); if (op!=null){ ops.add(op); } } } } if (!ops.isEmpty()){ // Do the actual database modifications try { ctx.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (RemoteException e) { if (logger.isActivated()){ logger.error("Something went wrong when updating the database with the contact info",e); } throw new ContactsManagerException(e.getMessage()); } catch (OperationApplicationException e) { if (logger.isActivated()){ logger.error("Something went wrong when updating the database with the contact info",e); } throw new ContactsManagerException(e.getMessage()); } } } /** * Set the photo-icon of a contact in the EAB * * @param contact Contact * @param photoIcon PhotoIcon * @throws ContactsManagerException */ public void setContactPhotoIcon(String contact, PhotoIcon photoIcon) throws ContactsManagerException { if (logger.isActivated()) { logger.info("Set photo-icon for contact " + contact); } if (!contact.equalsIgnoreCase(MYSELF)){ // May be called from outside the core, so be sure the number format is international before doing the queries contact = PhoneUtils.extractNumberFromUri(contact); } ContactInfo oldInfo = getContactInfo(contact); ContactInfo newInfo = new ContactInfo(oldInfo); // Get the presence info and modify the photo icon PresenceInfo presenceInfo = newInfo.getPresenceInfo(); presenceInfo.setPhotoIcon(photoIcon); newInfo.setPresenceInfo(presenceInfo); // Set the new info setContactInfo(newInfo, oldInfo); } /** * Remove the photo-icon of a contact in the EAB * * @param contact Contact * @param photoIcon PhotoIcon * @throws ContactsManagerException */ public void removeContactPhotoIcon(String contact) throws ContactsManagerException { if (logger.isActivated()) { logger.info("Remove the photo-icon for contact " + contact); } if (!contact.equalsIgnoreCase(MYSELF)){ // May be called from outside the core, so be sure the number format is international before doing the queries contact = PhoneUtils.extractNumberFromUri(contact); } ContactInfo oldInfo = getContactInfo(contact); ContactInfo newInfo = new ContactInfo(oldInfo); // Get the presence info and remove the photo icon PresenceInfo presenceInfo = newInfo.getPresenceInfo(); presenceInfo.setPhotoIcon(null); newInfo.setPresenceInfo(presenceInfo); // Set the new info setContactInfo(newInfo, oldInfo); } /** * Get the infos of a contact in the EAB * * @param contact Contact * @return Contact info */ public ContactInfo getContactInfo(String contact) { // May be called from outside the core, so be sure the number format is international before doing the queries contact = PhoneUtils.extractNumberFromUri(contact); ContactInfo infos = new ContactInfo(); infos.setRcsStatus(ContactInfo.NO_INFO); infos.setRcsStatusTimestamp(System.currentTimeMillis()); infos.setContact(contact); Capabilities capabilities = new Capabilities(); PresenceInfo presenceInfo = new PresenceInfo(); infos.setRegistrationState(ContactInfo.REGISTRATION_STATUS_UNKNOWN); Cursor cur = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, null, RichAddressBookData.KEY_CONTACT_NUMBER + "= ?", new String[]{contact}, null); if (cur != null) { if (cur.moveToFirst()) { // Get RCS Status infos.setRcsStatus(cur.getInt(cur.getColumnIndex(RichAddressBookData.KEY_RCS_STATUS))); infos.setRcsStatusTimestamp(cur.getLong(cur.getColumnIndex(RichAddressBookData.KEY_RCS_STATUS_TIMESTAMP))); infos.setRegistrationState(cur.getInt(cur.getColumnIndex(RichAddressBookData.KEY_REGISTRATION_STATE))); // Get Presence info presenceInfo.setPresenceStatus(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_PRESENCE_SHARING_STATUS))); FavoriteLink favLink = new FavoriteLink( cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_PRESENCE_WEBLINK_NAME)), cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_PRESENCE_WEBLINK_URL))); presenceInfo.setFavoriteLink(favLink); presenceInfo.setFavoriteLinkUrl(favLink.getLink()); presenceInfo.setFreetext(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_PRESENCE_FREE_TEXT))); Geoloc geoloc = null; if (Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_PRESENCE_GEOLOC_EXIST_FLAG)))) { geoloc = new Geoloc( cur.getDouble(cur.getColumnIndex(RichAddressBookData.KEY_PRESENCE_GEOLOC_LATITUDE)), cur.getDouble(cur.getColumnIndex(RichAddressBookData.KEY_PRESENCE_GEOLOC_LONGITUDE)), cur.getDouble(cur.getColumnIndex(RichAddressBookData.KEY_PRESENCE_GEOLOC_ALTITUDE))); } presenceInfo.setGeoloc(geoloc); presenceInfo.setTimestamp(cur.getLong(cur.getColumnIndex(RichAddressBookData.KEY_PRESENCE_TIMESTAMP))); PhotoIcon photoIcon = null; if (Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_PRESENCE_PHOTO_EXIST_FLAG)))) { try { int rowId = cur.getInt(cur.getColumnIndex(RichAddressBookData.KEY_ID)); Uri photoUri = ContentUris.withAppendedId(RichAddressBookData.CONTENT_URI, rowId); String etag = cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_PRESENCE_PHOTO_ETAG)); InputStream stream = ctx.getContentResolver().openInputStream(photoUri); byte[] content = new byte[stream.available()]; stream.read(content, 0, content.length); Bitmap bmp = BitmapFactory.decodeByteArray(content, 0, content.length); if (bmp != null) { photoIcon = new PhotoIcon(content, bmp.getWidth(), bmp.getHeight(), etag); } } catch (FileNotFoundException e) { if (logger.isActivated()){ logger.error("Can't get the photo",e); } } catch (IOException e) { if (logger.isActivated()){ logger.error("Can't get the photo",e); } } } presenceInfo.setPhotoIcon(photoIcon); // Get the capabilities infos capabilities.setCsVideoSupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_CS_VIDEO)))); capabilities.setFileTransferSupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_FILE_TRANSFER)))); capabilities.setImageSharingSupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_IMAGE_SHARING)))); capabilities.setImSessionSupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_IM_SESSION)))); capabilities.setPresenceDiscoverySupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_PRESENCE_DISCOVERY)))); capabilities.setSocialPresenceSupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_SOCIAL_PRESENCE)))); capabilities.setGeolocationPushSupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_GEOLOCATION_PUSH)))); capabilities.setVideoSharingSupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_VIDEO_SHARING)))); capabilities.setIPVoiceCallSupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_IP_VOICE_CALL)))); capabilities.setIPVideoCallSupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_IP_VIDEO_CALL)))); capabilities.setFileTransferThumbnailSupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_FILE_TRANSFER_THUMBNAIL)))); capabilities.setFileTransferHttpSupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_FILE_TRANSFER_HTTP)))); capabilities.setFileTransferStoreForwardSupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_FILE_TRANSFER_SF)))); capabilities.setGroupChatStoreForwardSupport(Boolean.parseBoolean(cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_GROUP_CHAT_SF)))); // Set RCS extensions capability String extensions = cur.getString(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_EXTENSIONS)); if (extensions != null) { String[] extensionList = extensions.split(";"); for (int i=0;i<extensionList.length;i++){ if (extensionList[i].trim().length()>0){ capabilities.addSupportedExtension(extensionList[i]); } } } capabilities.setTimestamp(cur.getLong(cur.getColumnIndex(RichAddressBookData.KEY_CAPABILITY_TIMESTAMP))); } cur.close(); } infos.setPresenceInfo(presenceInfo); infos.setCapabilities(capabilities); return infos; } /** * Set the sharing status of a contact in the EAB * * @param contact Contact * @param status Sharing status * @param reason Reason associated to the status * @throws ContactsManagerException */ public void setContactSharingStatus(String contact, String status, String reason) throws ContactsManagerException { if (logger.isActivated()) { logger.info("Set sharing status for contact " + contact + " to "+status+ " with reason "+reason); } // May be called from outside the core, so be sure the number format is international before doing the queries contact= PhoneUtils.extractNumberFromUri(contact); if (!PhoneUtils.isGlobalPhoneNumber(contact)){ if (logger.isActivated()){ logger.debug(contact +" is not a RCS valid number"); } return; } try { // Get the current contact sharing status EAB database, if there is one int currentStatus = getContactSharingStatus(contact); final ContactInfo oldInfo = getContactInfo(contact); ContactInfo newInfo = new ContactInfo(oldInfo); if (currentStatus==-1 || currentStatus!=ContactInfo.RCS_CANCELLED){ // We already are in a given RCS state, different from cancelled /** * INVITED STATE */ if (currentStatus==ContactInfo.RCS_PENDING_OUT){ // State: we have invited the remote contact // We leave this state only on a "terminated/rejected" or an "active" status if(status.equalsIgnoreCase("terminated") && (reason != null) && reason.equalsIgnoreCase("rejected")){ // Contact has rejected our invitation, go back to RCS_CAPABLE state newInfo.setRcsStatus(ContactInfo.RCS_CAPABLE); setContactInfo(newInfo, oldInfo); if (logger.isActivated()) { logger.debug(contact + " has passed from \"pending_out\" to \"terminated/rejected\" state"); logger.debug("=> Remove contact entry from EAB"); } return; }else if (status.equalsIgnoreCase(PresenceInfo.RCS_ACTIVE)){ // Contact has accepted our invitation, we are now active // Set contact type from RCS-capable to RCS newInfo.setRcsStatus(ContactInfo.RCS_ACTIVE); setContactInfo(newInfo, oldInfo); if (logger.isActivated()) { logger.debug(contact + " has passed from \"pending_out\" to \"active\" state"); } return; } } /** * WILLING STATE */ if (currentStatus==ContactInfo.RCS_PENDING){ // State: we have been invited by the remote contact if(status.equalsIgnoreCase(PresenceInfo.RCS_ACTIVE)){ // We have accepted the invitation, we are now active newInfo.setRcsStatus(ContactInfo.RCS_ACTIVE); setContactInfo(newInfo, oldInfo); if (logger.isActivated()) { logger.debug(contact + " has passed from \"pending\" to \"active\" state"); } return; }else if (status.equalsIgnoreCase("terminated") && (reason != null) && reason.equalsIgnoreCase("giveup")){ // Contact has cancelled its invitation // Set contact type from RCS-capable to RCS newInfo.setRcsStatus(ContactInfo.RCS_CAPABLE); setContactInfo(newInfo, oldInfo); if (logger.isActivated()) { logger.debug(contact + " has passed from \"pending\" to \"terminated/giveup\" state"); logger.debug("=> Remove contact entry from EAB"); } return; }else if (status.equalsIgnoreCase(PresenceInfo.RCS_BLOCKED)){ // We have declined the invitation // Set contact type from RCS-capable to RCS newInfo.setRcsStatus(ContactInfo.RCS_BLOCKED); setContactInfo(newInfo, oldInfo); if (logger.isActivated()) { logger.debug(contact + " has passed from \"pending\" to \"blocked\" state"); } return; } } /** * ACTIVE STATE */ if (currentStatus==ContactInfo.RCS_ACTIVE){ // State: we have shared our profile with contact // We leave this state on a "terminated/rejected" status if(status.equalsIgnoreCase("terminated") && (reason != null) && reason.equalsIgnoreCase("rejected")){ // We have ended our profile sharing, destroy entry in EAB // Set contact type from RCS-capable to RCS newInfo.setRcsStatus(ContactInfo.RCS_CAPABLE); setContactInfo(newInfo, oldInfo); if (logger.isActivated()) { logger.debug(contact + " has passed from \"active\" to \"terminated/rejected\" state"); logger.debug("=> Remove contact entry from EAB"); } return; } // Or if we revoked the contact if (status.equalsIgnoreCase(PresenceInfo.RCS_REVOKED)){ // Set contact type from RCS-capable to RCS newInfo.setRcsStatus(ContactInfo.RCS_CAPABLE); setContactInfo(newInfo, oldInfo); if (logger.isActivated()) { logger.debug(contact + " has passed from \"active\" to \"revoked\" state"); logger.debug("=> Remove contact entry from EAB"); } return; } } /** * BLOCKED STATE */ if (currentStatus==ContactInfo.RCS_BLOCKED){ // State: we have blocked this contact // We leave this state only when user unblocks, ie destroys the entry return; } }else if (currentStatus==-1||(currentStatus==ContactInfo.RCS_CANCELLED)){ // We have no entry for contact in EAB or it was in cancelled state /** * NO ENTRY IN EAB */ if (status.equalsIgnoreCase(PresenceInfo.RCS_PENDING_OUT)){ // We invite contact to share presence // Contact has accepted our invitation, we are now active // Update entry in EAB // Set contact type from RCS-capable to RCS newInfo.setRcsStatus(ContactInfo.RCS_PENDING_OUT); setContactInfo(newInfo, oldInfo); if (logger.isActivated()) { logger.debug(contact + " has been added to the EAB with the \"pending_out\" state"); } return; } if (status.equalsIgnoreCase(PresenceInfo.RCS_ACTIVE)){ // We received an active state from the contact, but we have no entry for him in EAB yet // It may occur if the number was deleted from native EAB, or if there was an error when we deleted/modified it // or if we logged on this RCS account on a new phone // Set contact type from RCS-capable to RCS newInfo.setRcsStatus(ContactInfo.RCS_ACTIVE); setContactInfo(newInfo, oldInfo); if (logger.isActivated()) { logger.debug(contact + " has been added to the EAB with the \"active\" state"); } return; } if (status.equalsIgnoreCase(PresenceInfo.RCS_PENDING)){ // We received a "pending" notification => contact has invited us // Update entry in EAB // Set contact type from RCS-capable to RCS newInfo.setRcsStatus(ContactInfo.RCS_PENDING); setContactInfo(newInfo, oldInfo); if (logger.isActivated()) { logger.debug(contact + " has been added to the EAB with the \"pending\" state"); } return; } if (status.equalsIgnoreCase(PresenceInfo.RCS_BLOCKED)){ // We block the contact to prevent invitations from him // Update entry in EAB // Set contact type from RCS-capable to RCS newInfo.setRcsStatus(ContactInfo.RCS_BLOCKED); setContactInfo(newInfo, oldInfo); if (logger.isActivated()) { logger.debug(contact + " has been added to the EAB with the \"blocked\" state"); } return; } } } catch (Exception e) { if (logger.isActivated()) { logger.error("Internal exception", e); } throw new ContactsManagerException(e.getMessage()); } } /** * Get sharing status of a contact * * @param contact Contact * @return Status or -1 if contact not found or in case of error */ public int getContactSharingStatus(String contact) { if (logger.isActivated()) { logger.info("Get sharing status for contact " + contact); } // May be called from outside the core, so be sure the number format is international before doing the queries contact = PhoneUtils.extractNumberFromUri(contact); int result = -1; try { // Get this number status in address book provider Cursor cursor = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, new String[]{RichAddressBookData.KEY_PRESENCE_SHARING_STATUS}, RichAddressBookData.KEY_CONTACT_NUMBER + "=?", new String[]{contact}, null); // Changed by Deutsche Telekom if (cursor != null) { if (cursor.moveToFirst()) { result = cursor.getInt(0); } cursor.close(); } if (logger.isActivated()) { logger.debug("Sharing status is " + result); } } catch (Exception e) { if (logger.isActivated()) { logger.error("Internal exception", e); } } return result; } /** * Revoke a contact * * @param contact Contact * @throws ContactsManagerException */ public void revokeContact(String contact) throws ContactsManagerException { if (logger.isActivated()) { logger.info("Revoke contact " + contact); } try { ContactInfo oldInfo = getContactInfo(contact); ContactInfo newInfo = new ContactInfo(oldInfo); newInfo.setRcsStatus(ContactInfo.RCS_REVOKED); setContactInfo(newInfo, oldInfo); } catch (Exception e) { if (logger.isActivated()) { logger.error("Internal exception", e); } throw new ContactsManagerException(e.getMessage()); } } /** * Unrevoke a contact * * @param contact Contact * @throws ContactsManagerException */ public void unrevokeContact(String contact) throws ContactsManagerException { if (logger.isActivated()) { logger.info("Unrevoke contact " + contact); } try { // Go back to RCS_CAPABLE state ContactInfo oldInfo = getContactInfo(contact); ContactInfo newInfo = new ContactInfo(oldInfo); newInfo.setRcsStatus(ContactInfo.RCS_CAPABLE); setContactInfo(newInfo, oldInfo); } catch (Exception e) { if (logger.isActivated()) { logger.error("Internal exception", e); } throw new ContactsManagerException(e.getMessage()); } } /** * Block a contact * * @param contact Contact * @throws ContactsManagerException */ public void blockContact(String contact) throws ContactsManagerException { if (logger.isActivated()) { logger.info("Block contact " + contact); } try{ // Go to RCS_BLOCKED state ContactInfo oldInfo = getContactInfo(contact); ContactInfo newInfo = new ContactInfo(oldInfo); newInfo.setRcsStatus(ContactInfo.RCS_BLOCKED); setContactInfo(newInfo, oldInfo); } catch (Exception e) { if (logger.isActivated()) { logger.error("Internal exception", e); } throw new ContactsManagerException(e.getMessage()); } } /** * Unblock a contact * * @param contact Contact * @throws ContactsManagerException */ public void unblockContact(String contact) throws ContactsManagerException { if (logger.isActivated()) { logger.info("Unblock contact " + contact); } try { // Go back to RCS_CAPABLE state ContactInfo oldInfo = getContactInfo(contact); ContactInfo newInfo = new ContactInfo(oldInfo); newInfo.setRcsStatus(ContactInfo.RCS_CAPABLE); setContactInfo(newInfo, oldInfo); } catch (Exception e) { if (logger.isActivated()) { logger.error("Internal exception", e); } throw new ContactsManagerException(e.getMessage()); } } /** * Flush the rich address book provider */ public void flushContactProvider(){ String where = RichAddressBookData.KEY_CONTACT_NUMBER +"<> NULL"; ctx.getContentResolver().delete(RichAddressBookData.CONTENT_URI, where, null); } /** * Add, modify or delete a contact number to the rich address book provider * * @param contact * @param RCS status */ public void modifyRcsContactInProvider(String contact, int rcsStatus){ // Check if an add or a modify must be done Cursor cursor = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, new String[]{RichAddressBookData.KEY_ID}, RichAddressBookData.KEY_CONTACT_NUMBER + "=?", new String[]{contact}, null); long contactRowID = INVALID_ID; // Changed by Deutsche Telekom if (cursor != null) { if (cursor.moveToFirst()) { contactRowID = cursor.getLong(0); } cursor.close(); } ContentValues values = new ContentValues(); values.put(RichAddressBookData.KEY_CONTACT_NUMBER, contact); values.put(RichAddressBookData.KEY_PRESENCE_SHARING_STATUS, rcsStatus); values.put(RichAddressBookData.KEY_TIMESTAMP, System.currentTimeMillis()); if (contactRowID==INVALID_ID){ // Contact not present in provider, insert ctx.getContentResolver().insert(RichAddressBookData.CONTENT_URI, values); }else{ // Contact already present, update ctx.getContentResolver().update(RichAddressBookData.CONTENT_URI, values, RichAddressBookData.KEY_CONTACT_NUMBER +"=?", new String[]{contact}); } } /** * Get the RCS contacts in the rich address book provider which have a presence relationship with the user * * @return list containing all RCS contacts, "Me" item excluded */ public List<String> getRcsContactsWithSocialPresence(){ List<String> rcsNumbers = new ArrayList<String>(); // Filter the rcs status String selection = "(" + RichAddressBookData.KEY_RCS_STATUS + "<>? AND " + RichAddressBookData.KEY_RCS_STATUS + "<>? AND " + RichAddressBookData.KEY_RCS_STATUS + "<>? )"; String[] selectionArgs = { String.valueOf(ContactInfo.NO_INFO), String.valueOf(ContactInfo.RCS_CAPABLE), String.valueOf(ContactInfo.NOT_RCS), }; Cursor c = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, new String[] { RichAddressBookData.KEY_CONTACT_NUMBER}, selection, selectionArgs, null); // Changed by Deutsche Telekom if (c != null) { while (c.moveToNext()) { rcsNumbers.add(c.getString(0)); } c.close(); } return rcsNumbers; } /** * Get the RCS contacts in the contact contract provider * * @return list containing all RCS contacts */ public List<String> getRcsContacts(){ List<String> rcsNumbers = new ArrayList<String>(); String[] projection = { RichAddressBookData.KEY_CONTACT_NUMBER }; // Filter the rcs status String selection = "(" + RichAddressBookData.KEY_RCS_STATUS + "<>? AND " + RichAddressBookData.KEY_RCS_STATUS + "<>? )"; String[] selectionArgs = { String.valueOf(ContactInfo.NO_INFO), String.valueOf(ContactInfo.NOT_RCS), }; Cursor cur = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, projection, selection, selectionArgs, null); // Changed by Deutsche Telekom if (cur != null) { while (cur.moveToNext()) { String number = cur.getString(0); if (!rcsNumbers.contains(number)) { rcsNumbers.add(number); } } cur.close(); } return rcsNumbers; } /** * Get all the contacts in the rich address book provider * * @return list containing all contacts that have been at least queried once for capabilities */ public List<String> getAllContacts(){ List<String> numbers = new ArrayList<String>(); String[] projection = { RichAddressBookData.KEY_CONTACT_NUMBER }; Cursor cur = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, projection, null, null, null); // Changed by Deutsche Telekom if (cur != null) { while (cur.moveToNext()) { String number = cur.getString(0); if (!numbers.contains(number)) { numbers.add(number); } } cur.close(); } return numbers; } /** * Get blocked RCS contacts in the rich address book provider * * @return list containing all RCS blocked contacts */ public List<String> getRcsBlockedContacts(){ List<String> rcsNumbers = new ArrayList<String>(); Cursor c = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, new String[] { RichAddressBookData.KEY_CONTACT_NUMBER}, RichAddressBookData.KEY_PRESENCE_SHARING_STATUS + "=\""+PresenceInfo.RCS_BLOCKED+"\"", null, null); // Changed by Deutsche Telekom if (c != null) { while (c.moveToNext()) { rcsNumbers.add(c.getString(0)); } c.close(); } return rcsNumbers; } /** * Get Invited RCS contacts in the rich address book provider * * @return list containing all RCS invited contacts */ public List<String> getRcsInvitedContacts(){ List<String> rcsNumbers = new ArrayList<String>(); Cursor c = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, new String[] { RichAddressBookData.KEY_CONTACT_NUMBER}, RichAddressBookData.KEY_PRESENCE_SHARING_STATUS + "=\""+PresenceInfo.RCS_PENDING_OUT+"\"", null, null); // Changed by Deutsche Telekom if (c != null) { while (c.moveToNext()) { rcsNumbers.add(c.getString(0)); } c.close(); } return rcsNumbers; } /** * Get Willing RCS contacts in the rich address book provider * * @return list containing all RCS willing contacts */ public List<String> getRcsWillingContacts(){ List<String> rcsNumbers = new ArrayList<String>(); Cursor c = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, new String[] { RichAddressBookData.KEY_CONTACT_NUMBER}, RichAddressBookData.KEY_PRESENCE_SHARING_STATUS + "=\""+PresenceInfo.RCS_PENDING+"\"", null, null); // Changed by Deutsche Telekom if (c != null) { while (c.moveToNext()) { rcsNumbers.add(c.getString(0)); } c.close(); } return rcsNumbers; } /** * Get Canceled RCS contacts in the rich address book provider * * @return list containing all RCS cancelled contacts */ public List<String> getRcsCancelledContacts(){ List<String> rcsNumbers = new ArrayList<String>(); Cursor c = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, new String[] { RichAddressBookData.KEY_CONTACT_NUMBER}, RichAddressBookData.KEY_PRESENCE_SHARING_STATUS + "=\""+PresenceInfo.RCS_CANCELLED+"\"", null, null); // Changed by Deutsche Telekom if (c != null) { while (c.moveToNext()) { rcsNumbers.add(c.getString(0)); } c.close(); } return rcsNumbers; } /** * Remove a cancelled invitation in the rich address book provider * * @param contact */ public void removeCancelledPresenceInvitation(String contact){ // Remove entry from rich address book provider ctx.getContentResolver().delete(RichAddressBookData.CONTENT_URI, RichAddressBookData.KEY_CONTACT_NUMBER +"=?" + " AND " + RichAddressBookData.KEY_PRESENCE_SHARING_STATUS + "=?", new String[]{contact, Integer.toString(ContactInfo.RCS_CANCELLED)}); } /** * Is the number in the RCS blocked list * * @param number Number to check * @return boolean */ public boolean isNumberBlocked(String number) { // Get this number status in address book int status = getContactSharingStatus(number); if (status==ContactInfo.RCS_BLOCKED){ return true; }else{ return false; } } /** * Is the number in the RCS buddy list * * @param number Number to check * @return boolean */ public boolean isNumberShared(String number) { // Get this number status in address book provider int status = -1; Cursor cursor = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, new String[]{RichAddressBookData.KEY_PRESENCE_SHARING_STATUS}, RichAddressBookData.KEY_CONTACT_NUMBER + "=?", new String[]{number}, null); // Changed by Deutsche Telekom if (cursor != null) { if (cursor.moveToFirst()) { status = cursor.getInt(0); } cursor.close(); } if (status==ContactInfo.RCS_ACTIVE){ return true; }else{ return false; } } /** * Has the number been invited to RCS * * @param number Number to check * @return boolean */ public boolean isNumberInvited(String number) { // Get this number status in address book provider int status = -1; Cursor cursor = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, new String[]{RichAddressBookData.KEY_PRESENCE_SHARING_STATUS}, RichAddressBookData.KEY_CONTACT_NUMBER + "=?", new String[]{number}, null); // Changed by Deutsche Telekom if (cursor != null) { if (cursor.moveToFirst()) { status = cursor.getInt(0); } cursor.close(); } if (status==ContactInfo.RCS_PENDING){ return true; }else{ return false; } } /** * Has the number invited us to RCS * * @param number Number to check * @return boolean */ public boolean isNumberWilling(String number) { // Get this number status in address book provider int status = -1; Cursor cursor = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, new String[]{RichAddressBookData.KEY_PRESENCE_SHARING_STATUS}, RichAddressBookData.KEY_CONTACT_NUMBER + "=?", new String[]{number}, null); // Changed by Deutsche Telekom if (cursor != null) { if (cursor.moveToFirst()) { status = cursor.getInt(0); } cursor.close(); } if (status==ContactInfo.RCS_PENDING_OUT){ return true; }else{ return false; } } /** * Has the number invited us to RCS then be cancelled * * @param number Number to check * @return boolean */ public boolean isNumberCancelled(String number) { // Get this number status in address book provider int status = -1; Cursor cursor = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, new String[]{RichAddressBookData.KEY_PRESENCE_SHARING_STATUS}, RichAddressBookData.KEY_CONTACT_NUMBER + "=?", new String[]{number}, null); // Changed by Deutsche Telekom if (cursor != null) { if (cursor.moveToFirst()) { status = cursor.getInt(0); } cursor.close(); } if (status==ContactInfo.RCS_CANCELLED){ return true; }else{ return false; } } /** * Modify the contact type for the contact * * @param rawContactId Raw contact id of the RCS contact * @param number RCS number of the contact * @param newContactType * @param oldContactType * @return list of ContentProviderOperation to be done */ private ArrayList<ContentProviderOperation> modifyContactTypeForContact(long rawContactId, String rcsNumber, int newContactType, int oldContactType){ if (newContactType==oldContactType){ // Nothing to do return new ArrayList<ContentProviderOperation>(); } // Update data in rich address book provider modifyRcsContactInProvider(rcsNumber, newContactType); ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); switch(newContactType){ case ContactInfo.NOT_RCS:{ // We are now not RCS if (oldContactType==ContactInfo.RCS_CAPABLE){ // Remove mime-type capable ops.add(deleteMimeTypeForContact(rawContactId, rcsNumber, MIMETYPE_RCS_CAPABLE_CONTACT)); }else if (oldContactType==ContactInfo.RCS_ACTIVE){ // Remove mime-type rcs active ops.add(deleteMimeTypeForContact(rawContactId, rcsNumber, MIMETYPE_RCS_CONTACT)); } // Add mime-type not capable ops.add(insertMimeTypeForContact(rawContactId, rcsNumber, MIMETYPE_NOT_RCS_CONTACT)); } break; case ContactInfo.RCS_ACTIVE:{ // We are now active if (oldContactType==ContactInfo.RCS_CAPABLE){ // Remove mime-type capable ops.add(deleteMimeTypeForContact(rawContactId, rcsNumber, MIMETYPE_RCS_CAPABLE_CONTACT)); }else if (oldContactType==ContactInfo.NOT_RCS || oldContactType==ContactInfo.NO_INFO){ // Remove mime-type not capable ops.add(deleteMimeTypeForContact(rawContactId, rcsNumber, MIMETYPE_NOT_RCS_CONTACT)); } // Add mime-type active ops.add(insertMimeTypeForContact(rawContactId, rcsNumber, MIMETYPE_RCS_CONTACT)); } break; default:{ // Other types : contact is RCS capable if (oldContactType==ContactInfo.NOT_RCS || oldContactType==ContactInfo.NO_INFO){ // Remove mime-type not capable active ops.add(deleteMimeTypeForContact(rawContactId, rcsNumber, MIMETYPE_NOT_RCS_CONTACT)); }else if (oldContactType==ContactInfo.RCS_ACTIVE){ // Remove mime-type active ops.add(deleteMimeTypeForContact(rawContactId, rcsNumber, MIMETYPE_RCS_CONTACT)); } // Add mime-type RCS capable ops.add(insertMimeTypeForContact(rawContactId, rcsNumber, MIMETYPE_RCS_CAPABLE_CONTACT)); } } // Update the RCS status row ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?", new String[]{String.valueOf(rawContactId), MIMETYPE_RCS_STATUS}) .withValue(Data.DATA2, newContactType) .build()); return ops; } /** * Modify the corresponding mimetype row for the contact * * @param rawContactId Raw contact id of the RCS contact * @param number RCS number of the contact * @param mimeType Mime type associated to the capability * @param newState True if the capability must be enabled, else false * @param oldState True if the capability was enabled, else false * @return ContentProviderOperation to be done */ private ContentProviderOperation modifyMimeTypeForContact(long rawContactId, String rcsNumber, String mimeType, boolean newState, boolean oldState){ if (newState==oldState){ // Nothing to do return null; } if (newState==true){ // We have to insert a new data in the raw contact return insertMimeTypeForContact(rawContactId, rcsNumber, mimeType); }else{ // We have to remove the data from the raw contact return deleteMimeTypeForContact(rawContactId, rcsNumber, mimeType); } } /** * Create (first time) the corresponding mimetype row for the contact * * @param rawContactId * @param rcsNumber * @param mimeType * @return ContentProviderOperation to be done */ private ContentProviderOperation createMimeTypeForContact(int rawContactId, String rcsNumber, String mimeType) { String mimeTypeDescription = getMimeTypeDescription(mimeType); if (mimeTypeDescription != null) { // Check if there is a mimetype description to be added return ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactId) .withValue(Data.MIMETYPE, mimeType) .withValue(Data.DATA1, rcsNumber) .withValue(Data.DATA2, mimeTypeDescription) .withValue(Data.DATA3, rcsNumber) .build(); } else { return ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactId) .withValue(Data.MIMETYPE, mimeType) .withValue(Data.DATA1, rcsNumber) .build(); } } /** * Insert the corresponding mimetype row for the contact * * @param rawContactId * @param rcsNumber * @param mimeType * @return ContentProviderOperation to be done */ private ContentProviderOperation insertMimeTypeForContact(long rawContactId, String rcsNumber, String mimeType) { String mimeTypeDescription = getMimeTypeDescription(mimeType); if (mimeTypeDescription != null) { // Check if there is a mimetype description to be added return ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValue(Data.RAW_CONTACT_ID, rawContactId) .withValue(Data.MIMETYPE, mimeType) .withValue(Data.DATA1, rcsNumber) .withValue(Data.DATA2, mimeTypeDescription) .withValue(Data.DATA3, rcsNumber) .build(); } else { return ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValue(Data.RAW_CONTACT_ID, rawContactId) .withValue(Data.MIMETYPE, mimeType) .withValue(Data.DATA1, rcsNumber) .build(); } } /** * Remove the corresponding mimetype row for the contact * * @param rawContactId * @param rcsNumber * @param mimeType * @return ContentProviderOperation to be done */ private ContentProviderOperation deleteMimeTypeForContact(long rawContactId, String rcsNumber, String mimeType){ // We have to remove a data from the raw contact return ContentProviderOperation.newDelete(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?" + " AND "+ Data.DATA1 + "=?", new String[]{String.valueOf(rawContactId), mimeType, rcsNumber}) .build(); } /** * Modify the registration state for the contact * * @param rawContactId Raw contact id of the RCS contact * @param number RCS number of the contact * @param newRegistrationState * @param oldRegistrationState * @param newFreeText * @param oldFreeText * @return list of ContentProviderOperations to be done */ private ArrayList<ContentProviderOperation> modifyContactRegistrationState(long rawContactId, String rcsNumber, int newRegistrationState, int oldRegistrationState, String newFreeText, String oldFreeText){ ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); boolean registrationChanged = true; if ((newRegistrationState==oldRegistrationState || newRegistrationState==ContactInfo.REGISTRATION_STATUS_UNKNOWN)){ registrationChanged = false; } if (registrationChanged){ // Modify registration status ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?" + " AND " + Data.DATA1 + "=?", new String[]{Long.toString(rawContactId), MIMETYPE_REGISTRATION_STATE, rcsNumber}) .withValue(Data.DATA2, newRegistrationState) .build()); } if (stringsHaveChanged(newFreeText, oldFreeText) || registrationChanged){ int availability = PRESENCE_STATUS_NOT_SET; if (newRegistrationState==ContactInfo.REGISTRATION_STATUS_ONLINE){ availability = PRESENCE_STATUS_ONLINE; }else if (newRegistrationState==ContactInfo.REGISTRATION_STATUS_OFFLINE){ availability = PRESENCE_STATUS_OFFLINE; } // Get the id of the status update data linked to this raw contact id String[] projection = {Data._ID, Data.RAW_CONTACT_ID}; long dataId = INVALID_ID; String selection = Data.RAW_CONTACT_ID + "=?"; String[] selectionArgs = { Long.toString(rawContactId)}; Cursor cur = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, null); // Changed by Deutsche Telekom if (cur != null) { if (cur.moveToNext()) { dataId = cur.getLong(0); } cur.close(); } ops.add(ContentProviderOperation.newInsert(StatusUpdates.CONTENT_URI) .withValue(StatusUpdates.DATA_ID, dataId) .withValue(StatusUpdates.STATUS, newFreeText) .withValue(StatusUpdates.STATUS_RES_PACKAGE, ctx.getPackageName()) .withValue(StatusUpdates.STATUS_LABEL, R.string.rcs_core_account_id) .withValue(StatusUpdates.STATUS_ICON, R.drawable.rcs_icon) .withValue(StatusUpdates.PRESENCE, availability) // Needed for inserting PRESENCE .withValue(StatusUpdates.PROTOCOL, Im.PROTOCOL_CUSTOM) .withValue(StatusUpdates.CUSTOM_PROTOCOL, " " /* Intentional left blank */) .withValue(StatusUpdates.STATUS_TIMESTAMP, System.currentTimeMillis()) .build()); } return ops; } /** * Modify the RCS extensions capability for the contact * * @param rawContactId Raw contact id of the RCS contact * @param number RCS number of the contact * @param newExtensions New extensions capabilities * @param oldExtensions Old extensions capabilities * @return list of contentProviderOperation to be done */ private List<ContentProviderOperation> modifyExtensionsCapabilityForContact(long rawContactId, String rcsNumber, ArrayList<String> newExtensions, ArrayList<String> oldExtensions){ List<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); // Compare the two lists of extensions if (newExtensions.containsAll(oldExtensions) && oldExtensions.containsAll(newExtensions)){ // Both lists have the same tags, no need to update return ops; } StringBuffer extension = new StringBuffer(); for (int j=0;j<newExtensions.size();j++){ extension.append(newExtensions.get(j)+";"); } // Check if we support at least one of the extensions this contact has boolean oldHasCommonExtensions = false; boolean newHasCommonExtensions = false; // Get my extensions String exts = RcsSettings.getInstance().getSupportedRcsExtensions(); if ((exts != null) && (exts.length() > 0)) { String[] ext = exts.split(","); for(int i=0; i < ext.length; i++) { String capability = ext[i]; if (newExtensions.contains(capability)){ newHasCommonExtensions = true; } if (oldExtensions.contains(capability)){ oldHasCommonExtensions = true; } } } // Add or remove the common extensions mimetype ops.add(modifyMimeTypeForContact(rawContactId, rcsNumber, MIMETYPE_CAPABILITY_COMMON_EXTENSION, newHasCommonExtensions, oldHasCommonExtensions)); // Update extensions ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?" + " AND " + Data.DATA1 + "=?", new String[]{String.valueOf(rawContactId), MIMETYPE_CAPABILITY_EXTENSIONS, rcsNumber}) .withValue(Data.DATA2, extension.toString()) .build()); return ops; } /** * Modify the presence info for a contact * * @param rawContactId Raw contact id of the RCS contact * @param number RCS number of the contact * @param newPresenceInfo * @param oldPresenceInfo * @return list of ContentProviderOperation to be done */ private ArrayList<ContentProviderOperation> modifyPresenceForContact(long rawContactId, String rcsNumber, PresenceInfo newPresenceInfo, PresenceInfo oldPresenceInfo){ ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); if (newPresenceInfo!=null && oldPresenceInfo!=null){ // Both are not null, check the differences and update fields if (newPresenceInfo.isOffline()!=oldPresenceInfo.isOffline() || newPresenceInfo.isOnline()!=oldPresenceInfo.isOnline()){ int availability = PRESENCE_STATUS_NOT_SET; if (newPresenceInfo.isOnline()){ availability = PRESENCE_STATUS_ONLINE; }else if (newPresenceInfo.isOffline()){ availability = PRESENCE_STATUS_OFFLINE; } // Modify the presence status ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?" + " AND " + Data.DATA1 + "=?", new String[]{String.valueOf(rawContactId), MIMETYPE_PRESENCE_STATUS, rcsNumber}) .withValue(Data.DATA2, availability) .build()); } if (stringsHaveChanged(newPresenceInfo.getFreetext(), oldPresenceInfo.getFreetext())){ // Modify the free text ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?" + " AND " + Data.DATA1 + "=?", new String[]{String.valueOf(rawContactId), MIMETYPE_FREE_TEXT, rcsNumber}) .withValue(Data.DATA2, newPresenceInfo.getFreetext()) .build()); } if (stringsHaveChanged(newPresenceInfo.getFavoriteLinkUrl(), oldPresenceInfo.getFavoriteLinkUrl())){ // Modify the web link ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?" + " AND " + Data.DATA1 + "=?", new String[]{String.valueOf(rawContactId), MIMETYPE_WEBLINK, rcsNumber}) .withValue(Data.DATA2, newPresenceInfo.getFavoriteLinkUrl()) .build()); //Add the weblink to the native @book ContentValues values = new ContentValues(); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, MIMETYPE_WEBLINK); values.put(Website.URL, newPresenceInfo.getFavoriteLinkUrl()); values.put(Website.TYPE, Website.TYPE_HOMEPAGE); values.put(Data.IS_PRIMARY, 1); values.put(Data.IS_SUPER_PRIMARY, 1); // Get the id of the current weblink mimetype long currentNativeWebLinkDataId = INVALID_ID; Cursor cur = ctx.getContentResolver().query(Data.CONTENT_URI, new String[]{ Data._ID }, Data.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=? AND " + Website.TYPE + "=?", new String[]{ Long.toString(rawContactId), MIMETYPE_WEBLINK, String.valueOf(Website.TYPE_HOMEPAGE) }, null); // Changed by Deutsche Telekom if (cur != null) { if (cur.moveToNext()) { currentNativeWebLinkDataId = cur.getLong(0); } cur.close(); } if (oldPresenceInfo.getFavoriteLinkUrl()==null){ // There was no weblink, insert ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValues(values) .build()); }else if (newPresenceInfo.getFavoriteLinkUrl()!=null){ // Update the existing weblink ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data._ID + "=?", new String[]{String.valueOf(currentNativeWebLinkDataId)}) .withValues(values) .build()); }else{ // Remove the existing weblink ops.add(ContentProviderOperation.newDelete(Data.CONTENT_URI) .withSelection(Data._ID + "=?", new String[]{String.valueOf(currentNativeWebLinkDataId)}) .build()); } } // Set the photo-icon PhotoIcon oldPhotoIcon = oldPresenceInfo.getPhotoIcon(); PhotoIcon newPhotoIcon = newPresenceInfo.getPhotoIcon(); // Check if photo etags are the same between the two presenceInfo boolean haveSameEtags = false; String oldPhotoIconEtag = null; String newPhotoIconEtag = null; if (oldPhotoIcon!=null){ oldPhotoIconEtag = oldPhotoIcon.getEtag(); } if (newPhotoIcon!=null){ newPhotoIconEtag = newPhotoIcon.getEtag(); } if (oldPhotoIconEtag==null && newPhotoIconEtag==null){ haveSameEtags = true; }else if(oldPhotoIconEtag!=null && newPhotoIconEtag!=null){ haveSameEtags = (oldPhotoIconEtag.equalsIgnoreCase(newPhotoIconEtag)); } if (!haveSameEtags){ // Not the same etag, so photo changed // Replace photo and etag List<ContentProviderOperation> photoOps = setContactPhoto(rawContactId, newPhotoIcon, true); for (int i=0;i<photoOps.size();i++){ ContentProviderOperation op = photoOps.get(i); if (op!=null){ ops.add(op); } } } if (oldPresenceInfo.getTimestamp()!=newPresenceInfo.getTimestamp()){ // Update the presence timestamp ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?" + " AND " + Data.DATA1 + "=?", new String[]{String.valueOf(rawContactId), MIMETYPE_PRESENCE_TIMESTAMP, rcsNumber}) .withValue(Data.DATA2, newPresenceInfo.getTimestamp()) .build()); } } else if (newPresenceInfo!=null) { // The new presence info is not null but the old one was, add new fields int availability = ContactInfo.REGISTRATION_STATUS_UNKNOWN; if (newPresenceInfo.isOnline()){ availability = ContactInfo.REGISTRATION_STATUS_ONLINE; }else if (newPresenceInfo.isOffline()){ availability = ContactInfo.REGISTRATION_STATUS_OFFLINE; } // Add the presence status to native address book ArrayList<ContentProviderOperation> registrationStateOps = modifyContactRegistrationState(rawContactId, rcsNumber, availability, -1, newPresenceInfo.getFreetext(), ""); for (int i=0;i<registrationStateOps.size();i++){ ContentProviderOperation op = registrationStateOps.get(i); if (op!=null){ ops.add(op); } } // Insert presence status ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValue(Data.RAW_CONTACT_ID, rawContactId) .withValue(Data.MIMETYPE, MIMETYPE_PRESENCE_STATUS) .withValue(Data.DATA1, rcsNumber) .withValue(Data.DATA2, newPresenceInfo.getPresenceStatus()) .build()); // Insert presence free text ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValue(Data.RAW_CONTACT_ID, rawContactId) .withValue(Data.MIMETYPE, MIMETYPE_FREE_TEXT) .withValue(Data.DATA1, rcsNumber) .withValue(Data.DATA2, newPresenceInfo.getFreetext()) .build()); // Insert presence web link ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValue(Data.RAW_CONTACT_ID, rawContactId) .withValue(Data.MIMETYPE, MIMETYPE_WEBLINK) .withValue(Data.DATA1, rcsNumber) .withValue(Data.DATA2, newPresenceInfo.getFavoriteLinkUrl()) .build()); //Add the weblink to the native @book ContentValues values = new ContentValues(); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, MIMETYPE_WEBLINK); values.put(Website.URL, newPresenceInfo.getFavoriteLinkUrl()); values.put(Website.TYPE, Website.TYPE_HOMEPAGE); values.put(Data.IS_PRIMARY, 1); values.put(Data.IS_SUPER_PRIMARY, 1); // Get the id of the current weblink mimetype long currentNativeWebLinkDataId = INVALID_ID; Cursor cur = ctx.getContentResolver().query(Data.CONTENT_URI, new String[]{ Data._ID }, Data.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=? AND " + Website.TYPE + "=?", new String[]{ Long.toString(rawContactId), MIMETYPE_WEBLINK, String.valueOf(Website.TYPE_HOMEPAGE) }, null); // Changed by Deutsche Telekom if (cur != null) { if (cur.moveToNext()) { currentNativeWebLinkDataId = cur.getLong(0); } cur.close(); } if (oldPresenceInfo.getFavoriteLinkUrl()==null){ // There was no weblink, insert ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValues(values) .build()); }else if (newPresenceInfo.getFavoriteLinkUrl()!=null){ // Update the existing weblink ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data._ID + "=?", new String[]{String.valueOf(currentNativeWebLinkDataId)}) .withValues(values) .build()); }else{ // Remove the existing weblink ops.add(ContentProviderOperation.newDelete(Data.CONTENT_URI) .withSelection(Data._ID + "=?", new String[]{String.valueOf(currentNativeWebLinkDataId)}) .build()); } // Set the photo List<ContentProviderOperation> photoOps = setContactPhoto(rawContactId, newPresenceInfo.getPhotoIcon(), true); for (int i=0;i<photoOps.size();i++){ ContentProviderOperation op = photoOps.get(i); if (op!=null){ ops.add(op); } } // Update timestamp if (oldPresenceInfo.getTimestamp()!=newPresenceInfo.getTimestamp()){ // Update the presence timestamp ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?" + " AND " + Data.DATA1 + "=?", new String[]{String.valueOf(rawContactId), MIMETYPE_PRESENCE_TIMESTAMP, rcsNumber}) .withValue(Data.DATA2, newPresenceInfo.getTimestamp()) .build()); } } else if (oldPresenceInfo!=null) { // The new presence info is null but the old one was not, remove fields // Remove the presence status to native address book // Force presence status to offline and free text to null ArrayList<ContentProviderOperation> registrationStateOps = modifyContactRegistrationState(rawContactId, rcsNumber, ContactInfo.REGISTRATION_STATUS_OFFLINE, -1, "", oldPresenceInfo.getFreetext()); for (int i=0;i<registrationStateOps.size();i++){ ContentProviderOperation op = registrationStateOps.get(i); if (op!=null){ ops.add(op); } } // Remove presence status ops.add(ContentProviderOperation.newDelete(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?" + " AND " + Data.DATA1 + "=?", new String[]{String.valueOf(rawContactId), MIMETYPE_PRESENCE_STATUS, rcsNumber}) .build()); // Remove presence free text ops.add(ContentProviderOperation.newDelete(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?" + " AND " + Data.DATA1 + "=?", new String[]{String.valueOf(rawContactId), MIMETYPE_FREE_TEXT, rcsNumber}) .build()); // Remove presence web link ops.add(ContentProviderOperation.newDelete(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?" + " AND " + Data.DATA1 + "=?", new String[]{String.valueOf(rawContactId), MIMETYPE_WEBLINK, rcsNumber}) .build()); //Remove presence web link in native address book //Add the weblink to the native @book // Get the id of the current weblink mimetype long currentNativeWebLinkDataId = INVALID_ID; Cursor cur = ctx.getContentResolver().query(Data.CONTENT_URI, new String[]{ Data._ID }, Data.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=? AND " + Website.TYPE + "=?", new String[]{ Long.toString(rawContactId), MIMETYPE_WEBLINK, String.valueOf(Website.TYPE_HOMEPAGE) }, null); // Changed by Deutsche Telekom if (cur != null) { if (cur.moveToNext()) { currentNativeWebLinkDataId = cur.getLong(0); } cur.close(); } ops.add(ContentProviderOperation.newDelete(Data.CONTENT_URI) .withSelection(Data._ID + "=?", new String[]{String.valueOf(currentNativeWebLinkDataId)}) .build()); // Set the photo List<ContentProviderOperation> photoOps = setContactPhoto(rawContactId, null, true); for (int i=0;i<photoOps.size();i++){ ContentProviderOperation op = photoOps.get(i); if (op!=null){ ops.add(op); } } // Update the presence timestamp ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?" + " AND " + Data.DATA1 + "=?", new String[]{String.valueOf(rawContactId), MIMETYPE_PRESENCE_TIMESTAMP, rcsNumber}) .withValue(Data.DATA2, System.currentTimeMillis()) .build()); } return ops; } /** * Check if strings have changed * * @param new string * @param old string * @return true if the string are the same, else false */ private boolean stringsHaveChanged(String newString, String oldString){ if (newString == null) { if (oldString == null) { // Both are null return false; } else { // One string is null and not the other one return true; } } else { if (oldString == null) { // One string is null and not the other one return true; } else { // Both strings are not null, compare return (!newString.equalsIgnoreCase(oldString)); } } } /** * Get description associated to a MIME type. This string will be visible in the contact card * * @param mimeType MIME type * @return String */ private String getMimeTypeDescription(String mimeType){ if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_FILE_TRANSFER)) { return ctx.getString(R.string.rcs_core_contact_file_transfer); } else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_IM_SESSION)) { return ctx.getString(R.string.rcs_core_contact_im_session); } else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_CS_VIDEO)) { return ctx.getString(R.string.rcs_core_contact_cs_video); } else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_IP_VOICE_CALL)) { return ctx.getString(R.string.rcs_core_contact_ip_voice_call); } else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_IP_VIDEO_CALL)) { return ctx.getString(R.string.rcs_core_contact_ip_video_call); } else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_COMMON_EXTENSION)) { return ctx.getString(R.string.rcs_core_contact_extensions); } else return null; } /** * Get a list of numbers supporting Instant Messaging Session capability * * @return list containing all contacts that are "IM capable" */ public List<String> getImSessionCapableContacts() { List<String> IMCapableNumbers = new ArrayList<String>(); String[] projection = {Data.DATA1, Data.MIMETYPE}; String selectionImCapOff = Data.MIMETYPE + "=?"; String[] selectionArgsImCapOff = { MIMETYPE_CAPABILITY_IM_SESSION }; Cursor c = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selectionImCapOff, selectionArgsImCapOff, null); // Changed by Deutsche Telekom if (c != null) { while (c.moveToNext()) { String imCapableNumber = c.getString(0); if (!IMCapableNumbers.contains(imCapableNumber)) { IMCapableNumbers.add(imCapableNumber); } } c.close(); } return IMCapableNumbers; } /** * Get a list of numbers that can use richcall features * <li>These are the contacts that can do either image sharing, video sharing, or both * * @return list containing all contacts that can use richcall features */ public List<String> getRichcallCapableContacts() { List<String> richcallCapableNumbers = new ArrayList<String>(); String[] projection = {Data.DATA1, Data.MIMETYPE}; String selection = "( " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? )"; // Get all image sharing or video sharing capable contacts String[] selectionArgs = { MIMETYPE_CAPABILITY_IMAGE_SHARING, MIMETYPE_CAPABILITY_VIDEO_SHARING }; Cursor c = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, null); // Changed by Deutsche Telekom if (c != null) { while (c.moveToNext()) { String richcallCapableNumber = c.getString(0); if (!richcallCapableNumbers.contains(richcallCapableNumber)) { richcallCapableNumbers.add(richcallCapableNumber); } } c.close(); } return richcallCapableNumbers; } /** * Get a list of numbers that can use IP voice call features * * @return List containing all contacts that can use IP voice call features */ public List<String> getIPVoiceCallCapableContacts() { List<String> ipVoiceCallCapableNumbers = new ArrayList<String>(); String[] projection = {Data.DATA1, Data.MIMETYPE}; String selection = "( " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? )"; // Get all IP call capable contacts String[] selectionArgs = { MIMETYPE_CAPABILITY_IP_VOICE_CALL }; Cursor c = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, null); if (c != null) { while (c.moveToNext()) { String ipVoiceCallCapableNumber = c.getString(0); if (!ipVoiceCallCapableNumbers.contains(ipVoiceCallCapableNumber)) { ipVoiceCallCapableNumbers.add(ipVoiceCallCapableNumber); } } c.close(); } return ipVoiceCallCapableNumbers; } /** * Get a list of numbers that can use ip video call features * * @return list containing all contacts that can use ip video call features */ public List<String> getIPVideoCallCapableContacts() { List<String> ipVideoCallCapableNumbers = new ArrayList<String>(); String[] projection = {Data.DATA1, Data.MIMETYPE}; String selection = "( " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? )"; // Get all ipcall capable contacts String[] selectionArgs = { MIMETYPE_CAPABILITY_IP_VIDEO_CALL }; Cursor c = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, null); if (c != null) { while (c.moveToNext()) { String ipVideoCallCapableNumber = c.getString(0); if (!ipVideoCallCapableNumbers.contains(ipVideoCallCapableNumber)) { ipVideoCallCapableNumbers.add(ipVideoCallCapableNumber); } } c.close(); } return ipVideoCallCapableNumbers; } /** * Get a list of numbers that are available (answered last OPTIONS check) * * @return list containing all contacts that are available */ public List<String> getAvailableContacts() { List<String> availableNumbers = new ArrayList<String>(); String[] projection = {Data.DATA1, Data.DATA2, Data.MIMETYPE}; String selection = Data.MIMETYPE + "=?"; // Get all registration state entries String[] selectionArgs = { MIMETYPE_REGISTRATION_STATE }; Cursor c = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, null); // Changed by Deutsche Telekom if (c != null) { while (c.moveToNext()) { String availableNumber = c.getString(0); int registrationState = c.getInt(1); if (registrationState == ContactInfo.REGISTRATION_STATUS_ONLINE && !availableNumbers.contains(availableNumber)) { // If the registration status is online, add the number to the list availableNumbers.add(availableNumber); } } c.close(); } return availableNumbers; } /** * Is the contact RCS active * * @param contact * @return boolean */ public boolean isContactRcsActive(String contact){ contact = PhoneUtils.extractNumberFromUri(contact); if (isNumberShared(contact)){ return true; }else{ return false; } } /** * Set contact capabilities * * @param contact Contact * @param capabilities Capabilities * @param contactType Contact type * @param registrationState Three possible values : online/offline/unknown */ public void setContactCapabilities(String contact, Capabilities capabilities, int contactType, int registrationState) { contact = PhoneUtils.extractNumberFromUri(contact); // Get the current information on this contact ContactInfo oldInfo = getContactInfo(contact); ContactInfo newInfo = new ContactInfo(oldInfo); // Set the contact type newInfo.setRcsStatus(contactType); // Set the registration state newInfo.setRegistrationState(registrationState); // Modify the capabilities regarding the registration state boolean isRegistered = (registrationState==ContactInfo.REGISTRATION_STATUS_ONLINE); // Cs Video capabilities.setCsVideoSupport(capabilities.isCsVideoSupported() && isRegistered); // File transfer. This capability is enabled: // - if the capability is present and the contact is registered // - if the FT CAP ALWAYS ON is enabled and the contact is RCS capable capabilities.setFileTransferSupport((capabilities.isFileTransferSupported() && isRegistered) || (RcsSettings.getInstance().isFtAlwaysOn() && newInfo.isRcsContact())); // Image sharing capabilities.setImageSharingSupport(capabilities.isImageSharingSupported() && isRegistered); // IM session. This capability is enabled: // - if the capability is present and the contact is registered // - if the IM S&F is enabled and the contact is RCS capable capabilities.setImSessionSupport((capabilities.isImSessionSupported() && isRegistered) || (RcsSettings.getInstance().isImAlwaysOn() && newInfo.isRcsContact())); // Video sharing capabilities.setVideoSharingSupport(capabilities.isVideoSharingSupported() && isRegistered); // Geolocation push capabilities.setGeolocationPushSupport(capabilities.isGeolocationPushSupported() && isRegistered); // FT thumbnail capabilities.setFileTransferThumbnailSupport(capabilities.isFileTransferThumbnailSupported() && isRegistered); // FT HTTP // - if the capability is present and the contact is registered or IM always on is activated final boolean isFileTransferHttpAllowed = (isRegistered || (RcsSettings.getInstance().isImAlwaysOn())); capabilities.setFileTransferHttpSupport((capabilities.isFileTransferHttpSupported() && isFileTransferHttpAllowed)); // FT S&F capabilities.setFileTransferStoreForwardSupport(capabilities.isFileTransferStoreForwardSupported() && isRegistered); // Group chat S&F capabilities.setGroupChatStoreForwardSupport(capabilities.isGroupChatStoreForwardSupported() && isRegistered); // IP Voice call capabilities.setIPVoiceCallSupport(capabilities.isIPVoiceCallSupported() && isRegistered); // IP Video call capabilities.setIPVideoCallSupport(capabilities.isIPVideoCallSupported() && isRegistered); // Add the capabilities newInfo.setCapabilities(capabilities); // Save the modifications try { setContactInfo(newInfo, oldInfo); } catch (ContactsManagerException e) { if (logger.isActivated()){ logger.error("Could not save the contact modifications",e); } } } /** * Set contact capabilities * * @param contact Contact * @param capabilities Capabilities */ public void setContactCapabilities(String contact, Capabilities capabilities) { contact = PhoneUtils.extractNumberFromUri(contact); // Get the current information on this contact ContactInfo oldInfo = getContactInfo(contact); ContactInfo newInfo = new ContactInfo(oldInfo); newInfo.setCapabilities(capabilities); // Save the modifications try { setContactInfo(newInfo, oldInfo); } catch (ContactsManagerException e) { if (logger.isActivated()){ logger.error("Could not save the contact modifications",e); } } } /** * Get contact capabilities * <br>If contact has never been enriched with capability, returns null * * @param contact * @return capabilities */ public Capabilities getContactCapabilities(String contact){ ContactInfo contactInfo = getContactInfo(contact); if (contactInfo.getRcsStatus()==ContactInfo.NO_INFO){ return null; }else{ return contactInfo.getCapabilities(); } } /** * Set contact capabilities timestamp * * @param contact * @param timestamp */ public void setContactCapabilitiesTimestamp(String contact, long timestamp){ if (logger.isActivated()){ logger.debug("Setting contact capabilities timestamp for "+contact +" to "+timestamp); } ContactInfo oldInfo = getContactInfo(contact); ContactInfo newInfo = new ContactInfo(oldInfo); Capabilities capabilities = newInfo.getCapabilities(); capabilities.setTimestamp(timestamp); newInfo.setCapabilities(capabilities); try { setContactInfo(newInfo, oldInfo); } catch (ContactsManagerException e) { if (logger.isActivated()){ logger.error("Could not update the contact capabilities timestamp",e); } } } /** * Modify the RCS capability timestamp for the contact * * @param rawContactId Raw contact id of the RCS contact * @param number RCS number of the contact * @param timestamp New timestamp * @return content */ private ContentProviderOperation modifyCapabilityTimestampForContact(long rawContactId, String rcsNumber, long timestamp){ return ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.RAW_CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?" + " AND " + Data.DATA1 + "=?", new String[]{Long.toString(rawContactId), MIMETYPE_CAPABILITY_TIMESTAMP, rcsNumber}) .withValue(Data.DATA2, timestamp) .build(); } /** * Utility method to create new "RCS" raw contact, that aggregates with other raw contact * * @param contact info for the RCS raw contact * @param id of the raw contact we want to aggregate the RCS infos to * @return the RCS rawContactId concerning this newly created contact */ public long createRcsContact(final ContactInfo info, final long rawContactId) { // If phone number can't be loosely compared with itself then we don't // make the phone number RCS. if (!phoneNumbersEqual(info.getContact(), info.getContact(), false)) { if (logger.isActivated()){ logger.debug("RCS contact could not be created loose comparison failed"); } return INVALID_ID; } if (logger.isActivated()){ logger.debug("Creating new RCS rawcontact for "+info.getContact()+" to be associated to rawContactId "+rawContactId); } ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); //Create rawcontact for RCS int rawContactRefIms = ops.size(); ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED) .withValue(RawContacts.ACCOUNT_TYPE, AuthenticationService.ACCOUNT_MANAGER_TYPE) .withValue(RawContacts.ACCOUNT_NAME, ctx.getString(R.string.rcs_core_account_username)) .build()); // Insert number ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, MIMETYPE_NUMBER) .withValue(Data.DATA1, info.getContact()) .build()); // Insert name, so it appears in case the contact is unlinked ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) .withValue(StructuredName.DISPLAY_NAME, info.getContact()) .build()); // Create RCS status row ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, ContactsManager.MIMETYPE_RCS_STATUS) .withValue(Data.DATA1, info.getContact()) .withValue(Data.DATA2, info.getRcsStatus()) .build()); // Create RCS status timestamp row ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, ContactsManager.MIMETYPE_RCS_STATUS_TIMESTAMP) .withValue(Data.DATA1, info.getContact()) .withValue(Data.DATA2, System.currentTimeMillis()) .build()); // Insert presence timestamp ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, MIMETYPE_PRESENCE_TIMESTAMP) .withValue(Data.DATA1, info.getContact()) .withValue(Data.DATA2, System.currentTimeMillis()) .build()); if (info.getPresenceInfo()!=null) { // Insert presence free text ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, MIMETYPE_FREE_TEXT) .withValue(Data.DATA1, info.getContact()) .withValue(Data.DATA2, info.getPresenceInfo().getFreetext()) .build()); // Insert presence status ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, MIMETYPE_PRESENCE_STATUS) .withValue(Data.DATA1, info.getContact()) .withValue(Data.DATA2, info.getPresenceInfo().getPresenceStatus()) .build()); // Insert presence web link //Add the weblink to the native @book ContentValues values = new ContentValues(); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, MIMETYPE_WEBLINK); values.put(Website.URL, info.getPresenceInfo().getFavoriteLinkUrl()); values.put(Website.TYPE, Website.TYPE_HOMEPAGE); values.put(Data.IS_PRIMARY, 1); values.put(Data.IS_SUPER_PRIMARY, 1); ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValues(values) .build()); } else { // No presence info // Insert presence free text ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, MIMETYPE_FREE_TEXT) .withValue(Data.DATA1, info.getContact()) .withValue(Data.DATA2, "") .build()); // Insert presence status ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, MIMETYPE_PRESENCE_STATUS) .withValue(Data.DATA1, info.getContact()) .withValue(Data.DATA2, PRESENCE_STATUS_NOT_SET) .build()); } // Insert capabilities timestamp ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, MIMETYPE_CAPABILITY_TIMESTAMP) .withValue(Data.DATA1, info.getContact()) .withValue(Data.DATA2, System.currentTimeMillis()) .build()); // Insert capabilities if present Capabilities capabilities = info.getCapabilities(); // Cs Video if (capabilities.isCsVideoSupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_CS_VIDEO)); } // File transfer if (capabilities.isFileTransferSupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_FILE_TRANSFER)); } // Image sharing if (capabilities.isImageSharingSupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_IMAGE_SHARING)); } // IM session if (capabilities.isImSessionSupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_IM_SESSION)); } // Video sharing if (capabilities.isVideoSharingSupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_VIDEO_SHARING)); } // IP Voice call if (capabilities.isIPVoiceCallSupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_IP_VOICE_CALL)); } // IP Video call if (capabilities.isIPVideoCallSupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_IP_VIDEO_CALL)); } // Presence discovery if (capabilities.isPresenceDiscoverySupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_PRESENCE_DISCOVERY)); } // Social presence if (capabilities.isSocialPresenceSupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_SOCIAL_PRESENCE)); } // Geolocation push if (capabilities.isGeolocationPushSupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_GEOLOCATION_PUSH)); } // File transfer thumbnail if (capabilities.isFileTransferThumbnailSupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_FILE_TRANSFER_THUMBNAIL)); } // File transfer HTTP if (capabilities.isFileTransferHttpSupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_FILE_TRANSFER_HTTP)); } // File transfer S&F if (capabilities.isFileTransferStoreForwardSupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_FILE_TRANSFER_SF)); } // Group chat S&F if (capabilities.isGroupChatStoreForwardSupported()) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_GROUP_CHAT_SF)); } // Insert extensions boolean hasCommonExtensions = false; StringBuffer extension = new StringBuffer(); ArrayList<String> newExtensions = info.getCapabilities().getSupportedExtensions(); String exts = RcsSettings.getInstance().getSupportedRcsExtensions(); for (int j = 0; j < newExtensions.size(); j++) { extension.append(newExtensions.get(j) +";"); // Check if we support at least one of the extensions this contact has // Get my extensions if ((exts != null) && (exts.length() > 0)) { String[] ext = exts.split(","); for(int i=0; i < ext.length; i++) { String capability = ext[i]; if (newExtensions.contains(capability)){ hasCommonExtensions = true; } } } } ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, MIMETYPE_CAPABILITY_EXTENSIONS) .withValue(Data.DATA1, info.getContact()) .withValue(Data.DATA2, extension.toString()) .withValue(Data.DATA3, info.getContact()) .build()); if (hasCommonExtensions) { // Insert common extensions item ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_CAPABILITY_COMMON_EXTENSION)); } // Insert registration status ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, MIMETYPE_REGISTRATION_STATE) .withValue(Data.DATA1, info.getContact()) .withValue(Data.DATA2, info.getRegistrationState()) .build()); // Insert contact type, it is either RCS active, RCS capable, not RCS or we have no info on it if (info.getRcsStatus()==ContactInfo.RCS_ACTIVE) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_RCS_CONTACT)); // Insert avatar, only if status is "active" // (we do not want a default RCS picture if we do not share our presence profile yet) Bitmap rcsAvatar = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.rcs_core_default_portrait_icon); byte[] iconData = convertBitmapToBytes(rcsAvatar); if (info.getPresenceInfo()!=null && info.getPresenceInfo().getPhotoIcon()!=null){ iconData = info.getPresenceInfo().getPhotoIcon().getContent(); } ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, ContactsManager.MIMETYPE_PHOTO) .withValue(Photo.PHOTO, iconData) .withValue(Data.IS_PRIMARY, 1) .build()); } else if (info.getRcsStatus()==ContactInfo.NOT_RCS) { ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_NOT_RCS_CONTACT)); } else if (info.getRcsStatus()!=ContactInfo.NO_INFO) { // In all other cases, contact is RCS capable ops.add(createMimeTypeForContact(rawContactRefIms, info.getContact(), MIMETYPE_RCS_CAPABLE_CONTACT)); } // Create the RCS raw contact and get its id long rcsRawContactId = INVALID_ID; try { ContentProviderResult[] results; results = ctx.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); rcsRawContactId = ContentUris.parseId(results[rawContactRefIms].uri); } catch (RemoteException e) { } catch (OperationApplicationException e) { return INVALID_ID; } // Aggregate the newly RCS raw contact and the raw contact that has the phone number ops.clear(); ops.add(ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI) .withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER) .withValue(AggregationExceptions.RAW_CONTACT_ID1, rcsRawContactId) .withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId).build()); try { ctx.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); // Add to exception provider ContentValues values = new ContentValues(); values.put(AggregationData.KEY_RAW_CONTACT_ID, rawContactId); values.put(AggregationData.KEY_RCS_RAW_CONTACT_ID, rcsRawContactId); values.put(AggregationData.KEY_RCS_NUMBER, info.getContact()); ctx.getContentResolver().insert(AggregationData.CONTENT_URI, values); } catch (RemoteException e) { if (logger.isActivated()){ logger.debug("Remote exception => "+e); } return INVALID_ID; } catch (OperationApplicationException e) { if (logger.isActivated()){ logger.debug("Operation exception => "+e); } return INVALID_ID; } return rcsRawContactId; } /** * Converts the specified bitmap to a byte array. * * @param bitmap the Bitmap to convert * @return the bitmap as bytes, null if converting fails. */ private byte[] convertBitmapToBytes(final Bitmap bitmap) { byte[] iconData = null; int size = bitmap.getRowBytes() * bitmap.getHeight(); ByteArrayOutputStream out = null; try { out = new ByteArrayOutputStream(size); if (bitmap.compress(Bitmap.CompressFormat.PNG, 0 /* quality ignored for PNG */, out)) { iconData = out.toByteArray(); } else { if (logger.isActivated()){ logger.debug("Unable to convert bitmap, compression failed"); } } } finally { CloseableUtils.close(out); } return iconData; } /** * Utility method to create the "Me" raw contact. * * @param context The application context. * @return the rawContactId of the newly created contact */ public long createMyContact() { RcsSettings.createInstance(ctx); if (!RcsSettings.getInstance().isSocialPresenceSupported()){ return INVALID_ID; } // Check if IMS account exists before continue AccountManager am = AccountManager.get(ctx); if (am.getAccountsByType(AuthenticationService.ACCOUNT_MANAGER_TYPE).length == 0) { if (logger.isActivated()){ logger.error("Could not create \"Me\" contact, no RCS account found"); } throw new IllegalStateException("No RCS account found"); } // Check if RCS raw contact for "Me" does not already exist long imsRawContactId = getRawContactIdForMe(); if (imsRawContactId != INVALID_ID) { if (logger.isActivated()){ logger.error("\"Me\" contact already exists, no need to recreate"); } }else{ if (logger.isActivated()){ logger.error("\"Me\" contact does not already exists, creating it"); } ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); //Create rawcontact for RCS int rawContactRefIms = ops.size(); ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) .withValue(RawContacts.ACCOUNT_TYPE, AuthenticationService.ACCOUNT_MANAGER_TYPE) .withValue(RawContacts.ACCOUNT_NAME, ctx.getString(R.string.rcs_core_account_username)) .withValue(RawContacts.SOURCE_ID, MYSELF) .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED) .build()); // Set name ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) .withValue(StructuredName.DISPLAY_NAME, ctx.getString(R.string.rcs_core_my_profile)) .build()); // Create RCS status row ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, ContactsManager.MIMETYPE_RCS_STATUS) .withValue(Data.DATA1, MYSELF) .withValue(Data.DATA2, ContactInfo.RCS_CAPABLE) .build()); // Create RCS status timestamp row ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, ContactsManager.MIMETYPE_RCS_STATUS_TIMESTAMP) .withValue(Data.DATA1, MYSELF) .withValue(Data.DATA2, System.currentTimeMillis()) .build()); // Create my profile shortcut ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, ContactsManager.MIMETYPE_SEE_MY_PROFILE) .withValue(Data.DATA1, MYSELF) .build()); // Insert presence timestamp ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, MIMETYPE_PRESENCE_TIMESTAMP) .withValue(Data.DATA1, MYSELF) .withValue(Data.DATA2, System.currentTimeMillis()) .build()); // Insert presence free text ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, MIMETYPE_FREE_TEXT) .withValue(Data.DATA1, MYSELF) .withValue(Data.DATA2, "") .build()); // Insert presence status ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, MIMETYPE_PRESENCE_STATUS) .withValue(Data.DATA1, MYSELF) .withValue(Data.DATA2, PRESENCE_STATUS_NOT_SET) .build()); // Insert capabilities timestamp ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, MIMETYPE_CAPABILITY_TIMESTAMP) .withValue(Data.DATA1, MYSELF) .withValue(Data.DATA2, System.currentTimeMillis()) .build()); // Insert default avatar Bitmap rcsAvatar = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.rcs_core_default_portrait_icon); byte[] iconData = convertBitmapToBytes(rcsAvatar); ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactRefIms) .withValue(Data.MIMETYPE, ContactsManager.MIMETYPE_PHOTO) .withValue(Photo.PHOTO, iconData) .withValue(Data.IS_PRIMARY, 1) .build()); try { ContentProviderResult[] results; results = ctx.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); imsRawContactId = ContentUris.parseId(results[rawContactRefIms].uri); } catch (RemoteException e) { imsRawContactId = INVALID_ID; } catch (OperationApplicationException e) { imsRawContactId = INVALID_ID; } ops.clear(); // Set default free text to null and availability to online ArrayList<ContentProviderOperation> registrationStateOps = modifyContactRegistrationState(imsRawContactId, MYSELF, ContactInfo.REGISTRATION_STATUS_ONLINE, -1, "", ""); for (int i=0;i<registrationStateOps.size();i++){ ContentProviderOperation op = registrationStateOps.get(i); if (op!=null){ ops.add(op); } } try { ctx.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (RemoteException e) { imsRawContactId = INVALID_ID; } catch (OperationApplicationException e) { imsRawContactId = INVALID_ID; } } return imsRawContactId; } /** * Utility to find the RCS rawContactIds for a specific phone number. * * @param phoneNumber the phoneNumber to search for * @return list of contactIds, empty list if none was found */ private List<Long> getRcsRawContactIdFromPhoneNumber(String phoneNumber) { List<Long> contactsIds = new ArrayList<Long>(); String[] projection = { Data.RAW_CONTACT_ID, Data.DATA1 }; String selection = Data.MIMETYPE + "=? AND PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + ", ?)"; String[] selectionArgs = { MIMETYPE_NUMBER, phoneNumber }; String sortOrder = Data.RAW_CONTACT_ID; Cursor cur = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, sortOrder); if (cur != null) { while (cur.moveToNext()) { long rcsRawContactId = cur.getLong(cur.getColumnIndex(Data.RAW_CONTACT_ID)); String phone = cur.getString(cur.getColumnIndex(Data.DATA1)); String interPhone = PhoneUtils.formatNumberToInternational(phone); if (phoneNumber.equals(interPhone)) { contactsIds.add(rcsRawContactId); break; } } cur.close(); } return contactsIds; } /** * Utility to find the rawContactIds for a specific phone number. * * @param phoneNumber the phoneNumber to search for * @return list of contactIds */ private List<Long> getRawContactIdsFromPhoneNumber(String phoneNumber) { List<Long> rawContactsIds = new ArrayList<Long>(); String[] projection = { Data.RAW_CONTACT_ID }; String selection = Data.MIMETYPE + "=? AND PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + ", ?)"; String[] selectionArgs = { Phone.CONTENT_ITEM_TYPE, phoneNumber }; String sortOrder = Data.RAW_CONTACT_ID; // Starting LOOSE equal Cursor cur = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, sortOrder); if (cur != null) { while (cur.moveToNext()) { long rawContactId = cur.getLong(cur.getColumnIndex(Data.RAW_CONTACT_ID)); if (!rawContactsIds.contains(rawContactId) && (!isSimAssociated(rawContactId) || (Build.VERSION.SDK_INT > 10))) { //Build.VERSION_CODES.GINGERBREAD_MR1 // We exclude the SIM only contacts, as they cannot be aggregated to a RCS raw contact // only if OS version if gingebread or fewer rawContactsIds.add(rawContactId); } } cur.close(); } /* No match found using LOOSE equals, starting STRICT equals. * * This is done because of that the PHONE_NUMBERS_EQUAL function in Android * dosent always return true when doing loose lookup of a phone number * against itself */ String selectionStrict = Data.MIMETYPE + "=? AND (NOT PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + ", ?) AND PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + ", ?, 1))"; String[] selectionArgsStrict = { Phone.CONTENT_ITEM_TYPE, phoneNumber, phoneNumber }; cur = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selectionStrict, selectionArgsStrict, sortOrder); if (cur != null) { while (cur.moveToNext()) { long rawContactId = cur.getLong(cur.getColumnIndex(Data.RAW_CONTACT_ID)); if (!rawContactsIds.contains(rawContactId) && (!isSimAssociated(rawContactId) || (Build.VERSION.SDK_INT > 10))) { //Build.VERSION_CODES.GINGERBREAD_MR1 // We exclude the SIM only contacts, as they cannot be aggregated to a RCS raw contact // only if OS version if gingebread or fewer rawContactsIds.add(rawContactId); } } cur.close(); } return rawContactsIds; } /** * Utility to get the RCS rawContact associated to a raw contact * * @param rawContactId the id of the rawContact * @param rcsNumber The RCS number * @return the id of the associated RCS rawContact */ public long getAssociatedRcsRawContact(final long rawContactId, final String rcsNumber) { long result = INVALID_ID; Cursor cursor = ctx.getContentResolver().query(AggregationData.CONTENT_URI, new String[]{AggregationData.KEY_RCS_RAW_CONTACT_ID, AggregationData.KEY_RCS_NUMBER, AggregationData.KEY_RAW_CONTACT_ID}, AggregationData.KEY_RCS_NUMBER + "=?" + " AND "+ AggregationData.KEY_RAW_CONTACT_ID + "=?", new String[]{rcsNumber, String.valueOf(rawContactId)}, null); // Changed by Deutsche Telekom if (cursor != null) { if (cursor.moveToFirst()) { result = cursor.getLong(0); } cursor.close(); } return result; } /** * Utility to check if a phone number is associated to an entry in the rich address book provider * * @param phoneNumber The phone number associated to the RCS contact * @return true if contact has an entry in the rich address book provider, else false */ public boolean isRcsAssociated(final String phoneNumber) { boolean result = false; Cursor cur = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, new String[]{RichAddressBookData.KEY_CONTACT_NUMBER}, RichAddressBookData.KEY_CONTACT_NUMBER + "=?", new String[]{phoneNumber}, null); if (cur!=null){ if (cur.moveToFirst()) { result = true; } cur.close(); }else{ result = false; } return result; } /** * Utility method to check if a raw contact is only associated to a SIM account * * @param rawContactId The id associated to the SIM account * @return true if the raw contact is only associated to a SIM account, else false */ public boolean isOnlySimAssociated(final String phoneNumber) { List<Long> rawContactIds = getRawContactIdsFromPhoneNumber(phoneNumber); for (int i=0;i<rawContactIds.size();i++){ Cursor rawCur = ctx.getContentResolver().query(RawContacts.CONTENT_URI, new String[]{RawContacts._ID}, "("+RawContacts.ACCOUNT_TYPE + " IS NULL OR "+RawContacts.ACCOUNT_TYPE +" <> \'"+SIM_ACCOUNT_NAME+"\') AND " + RawContacts._ID + "= "+ Long.toString(rawContactIds.get(i)), null, null); if (rawCur != null){ if (rawCur.getCount() > 0) { rawCur.close(); return false; } rawCur.close(); } } return true; } /** * Utility method to check if a raw contact id is a SIM account * * @param rawContactId * @return */ public boolean isSimAssociated(final long rawContactId){ boolean result = false; Cursor rawCur = ctx.getContentResolver().query(RawContacts.CONTENT_URI, new String[]{RawContacts._ID}, RawContacts.ACCOUNT_TYPE + "= \'"+SIM_ACCOUNT_NAME+"\' AND " + RawContacts._ID + "= "+ Long.toString(rawContactId), null, null); if (rawCur != null){ if (rawCur.getCount() > 0) { result=true; } rawCur.close(); } return result; } /** * Utility method to check if a raw contact id is a SIM account * * @param rawContactId * @return */ public boolean isSimAccount(final long rawContactId){ boolean result = false; Cursor rawCur = ctx.getContentResolver().query(RawContacts.CONTENT_URI, new String[]{RawContacts._ID}, RawContacts.ACCOUNT_TYPE + "= \'"+SIM_ACCOUNT_NAME+"\' AND " + RawContacts._ID + "= "+ Long.toString(rawContactId), null, null); if (rawCur != null){ if (rawCur.getCount() > 0) { result=true; } rawCur.close(); } return result; } /** * Utility to get access to Android's PHONE_NUMBERS_EQUAL SQL function. * * @note Impl and comments can be found in * /external/sqlite/android/PhoneNumberUtils.cpp * (phone_number_compare_inter) * * @param phone1 the first phone number * @param phone2 the second phone number * @param useStrictComparison set to false if loose comparison should be * used (normal), true if strict comparison should be used * @return true when equal */ private boolean phoneNumbersEqual(final String phone1, final String phone2, final boolean useStrictComparison) { boolean result = false; // Create a temporary db in memory to get access to the SQL engine SQLiteDatabase db = SQLiteDatabase.create(null); if (db == null) { throw new IllegalStateException("Could not retrieve db"); } // CSOFF: InlineConditionals String test = "SELECT CASE WHEN PHONE_NUMBERS_EQUAL(" + phone1 + "," + phone2 + "," + Integer.toString((useStrictComparison) ? 1 : 0) + ") " + "THEN 1 ELSE 0 END"; // CSON: InlineConditionals Cursor cur = db.rawQuery(test, null); if (cur != null){ if (cur.moveToNext()) { if (cur.getString(0).equals("1")) { result = true; } else { result = false; } } cur.close(); } db.close(); return result; } /** * Get the data id associated to a given mimeType for a contact. * * @param rawContactId the RCS rawcontact * @param mimeType The searched mimetype * @return The id of the data */ private long getDataIdForRawContact(final long rawContactId, final String mimeType) { long dataId = INVALID_ID; String[] projection = {Data._ID, Data.RAW_CONTACT_ID, Data.MIMETYPE }; String selection = Data.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?"; String[] selectionArgs = { Long.toString(rawContactId), mimeType }; Cursor cur = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, null); if (cur == null) { return INVALID_ID; } if (cur.moveToNext()) { dataId = cur.getLong(0); } cur.close(); return dataId; } /** * Get the rcs raw contact ids for the given contact * * @param contact The contact * @return list of raw contact ids associated to this contact */ private List<Long> getRcsRawContactIdsFromContact(final String contact){ if (MYSELF.equalsIgnoreCase(contact)){ List<Long> rawContactId = new ArrayList<Long>(); rawContactId.add(getRawContactIdForMe()); return rawContactId; }else{ return getRcsRawContactIdFromPhoneNumber(contact); } } /** * Utility to set the photo icon attribute on a RCS contact. * * @param rawContactId RCS rawcontact * @param photoIcon The photoIcon * @param makeSuperPrimary whether or not to set the super primary flag * @return */ private List<ContentProviderOperation> setContactPhoto(Long rawContactId, PhotoIcon photoIcon, boolean makeSuperPrimary) { List<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); // Get the photo data id String[] projection = { Data._ID }; String selection = Data.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?"; String[] selectionArgs = { Long.toString(rawContactId), Photo.CONTENT_ITEM_TYPE }; String sortOrder = Data._ID + " DESC"; Cursor cur = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, sortOrder); if (cur == null) { return ops; } byte[] iconData = null; if (photoIcon!=null){ iconData = photoIcon.getContent(); } // Insert default avatar if icon is null and it is not for myself if (iconData == null && rawContactId != getRawContactIdForMe()) { Bitmap rcsAvatar = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.rcs_core_default_portrait_icon); iconData = convertBitmapToBytes(rcsAvatar); } try { long dataId = INVALID_ID; if (iconData == null) { // May happen only for myself // Remove photoIcon if no data if (cur.moveToNext()) { dataId = cur.getLong(cur.getColumnIndex(Data._ID)); // Add delete operation ops.add(ContentProviderOperation.newDelete(Data.CONTENT_URI) .withSelection(Data._ID+"=?", new String[]{String.valueOf(dataId)}) .build()); } } else { ContentValues values = new ContentValues(); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, MIMETYPE_PHOTO); values.put(Photo.PHOTO, iconData); values.put(Data.IS_PRIMARY, 1); if (makeSuperPrimary) { values.put(Data.IS_SUPER_PRIMARY, 1); } if (cur.moveToNext()) { // We already had an icon, update it dataId = cur.getLong(cur.getColumnIndex(Data._ID)); ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data._ID+"=?", new String[]{String.valueOf(dataId)}) .withValues(values) .build()); } else { // We did not have an icon, insert a new one ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValues(values) .build()); } values.clear(); // Set etag values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, MIMETYPE_PHOTO_ETAG); String etag = null; if (photoIcon!=null){ etag = photoIcon.getEtag(); } values.put(Data.DATA2, etag); String[] projection2 = { Data._ID, Data.RAW_CONTACT_ID, Data.MIMETYPE }; String selection2 = Data.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?"; String[] selectionArgs2 = { Long.toString(rawContactId), MIMETYPE_PHOTO_ETAG }; Cursor cur2 = ctx.getContentResolver().query(Data.CONTENT_URI, projection2, selection2, selectionArgs2, null); // Changed by Deutsche Telekom if (cur2 == null) { return ops; } if (cur2.moveToNext()){ dataId = cur2.getLong(0); // We already had an etag, update it dataId = cur.getLong(cur.getColumnIndex(Data._ID)); ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data._ID+"=?", new String[]{String.valueOf(dataId)}) .withValues(values) .build()); }else{ // Insert etag ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValues(values) .build()); } cur2.close(); } } finally { cur.close(); } return ops; } /** * Utility to get the etag of a contact icon. * * @param contact * @return the icon etag */ public String getContactPhotoEtag(String contact) { String etag = null; contact = PhoneUtils.extractNumberFromUri(contact); List<Long> rawContactIds = getRcsRawContactIdsFromContact(contact); if (rawContactIds.isEmpty()){ return null; } // The data in all the rcs raw contacts is the same, so just take the first one long rawContactId = rawContactIds.get(0); String[] projection = { Data.DATA2 }; String selection = Data.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?"; String[] selectionArgs = { Long.toString(rawContactId), MIMETYPE_PHOTO_ETAG }; Cursor cur = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, null); // Changed by Deutsche Telekom if (cur != null) { if (cur.moveToNext()) { etag = cur.getString(0); } cur.close(); } return etag; } /** * Get the raw contact id of the "Me" contact. * * @return rawContactId */ private long getRawContactIdForMe() { String[] projection = { RawContacts.ACCOUNT_TYPE, RawContacts._ID, RawContacts.SOURCE_ID }; String selection = RawContacts.ACCOUNT_TYPE + "=? AND " + RawContacts.SOURCE_ID + "=?"; String[] selectionArgs = { AuthenticationService.ACCOUNT_MANAGER_TYPE, MYSELF }; Cursor cur = ctx.getContentResolver().query(RawContacts.CONTENT_URI, projection, selection, selectionArgs, null); if (cur == null) { return INVALID_ID; } if (!cur.moveToNext()) { cur.close(); return INVALID_ID; } long rawContactId = cur.getLong(1); cur.close(); return rawContactId; } /** * Mark the contact as "blocked for IM" * * @param contact * @param flag indicating if we enable or disable the IM sessions with the contact */ public void setImBlockedForContact(String contact, boolean flag){ // May be called from outside the core, so be sure the number format is international before doing the queries contact = PhoneUtils.extractNumberFromUri(contact); // Update the database // Get all the Ids from raw contacts that have this phone number List<Long> rawContactIds = getRawContactIdsFromPhoneNumber(contact); ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); // For each, prepare the modifications for (int i=0;i<rawContactIds.size();i++){ long rawContactId = rawContactIds.get(i); // Get the associated RCS raw contact id long rcsRawContactId = getAssociatedRcsRawContact(rawContactId, contact); if (rcsRawContactId==INVALID_ID){ // If no RCS raw contact id is associated to the raw contact, create a new one ContactInfo newInfo = new ContactInfo(); newInfo.setContact(contact); rcsRawContactId = createRcsContact(newInfo, rawContactId); } // Get the row id of this capability for this raw contact long dataId = getDataIdForRawContact(rawContactId, MIMETYPE_IM_BLOCKED); if (dataId == INVALID_ID) { // The capability is not present for now if (flag){ // We have to add it ops.add(insertMimeTypeForContact(rawContactId, contact, MIMETYPE_IM_BLOCKED)); } }else{ // The capability is present if (!flag){ // We have to remove it ops.add(deleteMimeTypeForContact(rawContactId, contact, MIMETYPE_IM_BLOCKED)); } } } if (!ops.isEmpty()){ // Do the actual database modifications try { ctx.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (RemoteException e) { if (logger.isActivated()){ logger.error("Something went wrong when updating the database with the contact info",e); } } catch (OperationApplicationException e) { if (logger.isActivated()){ logger.error("Something went wrong when updating the database with the contact info",e); } } } } // TODO : create here a "setIPCallBlockedForContact" /** * Get whether the "IM" feature is enabled or not for the contact * * @param contact * @return flag indicating if IM sessions with the contact are enabled or not */ public boolean isImBlockedForContact(String contact){ // May be called from outside the core, so be sure the number format is international before doing the queries contact = PhoneUtils.extractNumberFromUri(contact); String[] projection = {Data.DATA1, Data.MIMETYPE}; String selection = Data.MIMETYPE + "=?" + " AND " + Data.DATA1+ "=?"; String[] selectionArgs = { MIMETYPE_IM_BLOCKED , contact}; Cursor c = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, null); // Changed by Deutsche Telekom if (c != null) { if (c.getCount() > 0) { c.close(); return true; } c.close(); } return false; } // TODO : create here a "isIPCallBlockedForContact" /** * Get the contacts that are "IM blocked" * * @return list containing all contacts that are "IM blocked" */ public List<String> getImBlockedContacts(){ List<String> imBlockedNumbers = new ArrayList<String>(); String[] projection = {Data.DATA1, Data.MIMETYPE}; String selection = Data.MIMETYPE + "=?"; String[] selectionArgs = { MIMETYPE_IM_BLOCKED }; Cursor c = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, null); // Changed by Deutsche Telekom if (c != null) { while (c.moveToNext()) { imBlockedNumbers.add(c.getString(0)); } c.close(); } return imBlockedNumbers; } /** * Mark the contact as "blocked for FT" * * @param contact * @param flag indicating if we enable or disable the FT sessions with the contact */ public void setFtBlockedForContact(String contact, boolean flag){ // May be called from outside the core, so be sure the number format is international before doing the queries contact = PhoneUtils.extractNumberFromUri(contact); // Update the database // Get all the Ids from raw contacts that have this phone number List<Long> rawContactIds = getRawContactIdsFromPhoneNumber(contact); ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); // For each, prepare the modifications for (int i=0;i<rawContactIds.size();i++){ long rawContactId = rawContactIds.get(i); // Get the associated RCS raw contact id long rcsRawContactId = getAssociatedRcsRawContact(rawContactId, contact); if (rcsRawContactId==INVALID_ID){ // If no RCS raw contact id is associated to the raw contact, create a new one ContactInfo newInfo = new ContactInfo(); newInfo.setContact(contact); rcsRawContactId = createRcsContact(newInfo, rawContactId); } // Get the row id of this capability for this raw contact long dataId = getDataIdForRawContact(rawContactId, MIMETYPE_FT_BLOCKED); if (dataId == INVALID_ID) { // The capability is not present for now if (flag){ // We have to add it ops.add(insertMimeTypeForContact(rawContactId, contact, MIMETYPE_FT_BLOCKED)); } }else{ // The capability is present if (!flag){ // We have to remove it ops.add(deleteMimeTypeForContact(rawContactId, contact, MIMETYPE_FT_BLOCKED)); } } } if (!ops.isEmpty()){ // Do the actual database modifications try { ctx.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (RemoteException e) { if (logger.isActivated()){ logger.error("Something went wrong when updating the database with the contact info",e); } } catch (OperationApplicationException e) { if (logger.isActivated()){ logger.error("Something went wrong when updating the database with the contact info",e); } } } } /** * Get whether the "FT" feature is enabled or not for the contact * * @param contact * @return flag indicating if FT sessions with the contact are enabled or not */ public boolean isFtBlockedForContact(String contact){ // May be called from outside the core, so be sure the number format is international before doing the queries contact = PhoneUtils.extractNumberFromUri(contact); String[] projection = {Data.DATA1, Data.MIMETYPE}; String selection = Data.MIMETYPE + "=?" + " AND " + Data.DATA1+ "=?"; String[] selectionArgs = { MIMETYPE_FT_BLOCKED , contact}; Cursor c = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, null); if (c != null) { if (c.getCount() > 0) { c.close(); return true; } c.close(); } return false; } /** * Get the contacts that are "FT blocked" * * @return list containing all contacts that are "FT blocked" */ public List<String> getFtBlockedContacts(){ List<String> imBlockedNumbers = new ArrayList<String>(); String[] projection = {Data.DATA1, Data.MIMETYPE}; String selection = Data.MIMETYPE + "=?"; String[] selectionArgs = { MIMETYPE_FT_BLOCKED }; Cursor c = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, null); // Changed by Deutsche Telekom if (c != null) { while (c.moveToNext()) { imBlockedNumbers.add(c.getString(0)); } c.close(); } return imBlockedNumbers; } /** * Utility to create a ContactInfo object from a cursor containing data * * @param cursor * @return contactInfo */ private ContactInfo getContactInfoFromCursor(Cursor cursor){ ContactInfo contactInfo = new ContactInfo(); PresenceInfo presenceInfo = new PresenceInfo(); Capabilities capabilities = new Capabilities(); byte[] photoContent = null; String photoEtag = null; while(cursor.moveToNext()){ String mimeType = cursor.getString(cursor.getColumnIndex(Data.MIMETYPE)); if (mimeType.equalsIgnoreCase(MIMETYPE_WEBLINK)){ // Set weblink int columnIndex = cursor.getColumnIndex(Website.URL); if (columnIndex!=-1){ presenceInfo.setFavoriteLinkUrl(cursor.getString(columnIndex)); } }else if (mimeType.equalsIgnoreCase(MIMETYPE_PHOTO)){ // Set photo int columnIndex = cursor.getColumnIndex(Photo.PHOTO); if (columnIndex!=-1){ photoContent = cursor.getBlob(columnIndex); } }else if (mimeType.equalsIgnoreCase(MIMETYPE_PHOTO_ETAG)){ // Set photo etag int columnIndex = cursor.getColumnIndex(Data.DATA2); if (columnIndex!=-1){ photoEtag = cursor.getString(columnIndex); } }else if (mimeType.equalsIgnoreCase(MIMETYPE_PRESENCE_TIMESTAMP)){ // Set presence timestamp int columnIndex = cursor.getColumnIndex(Data.DATA2); if (columnIndex!=-1){ presenceInfo.setTimestamp(cursor.getLong(columnIndex)); } }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_TIMESTAMP)){ // Set capability timestamp int columnIndex = cursor.getColumnIndex(Data.DATA2); if (columnIndex!=-1){ capabilities.setTimestamp(cursor.getLong(columnIndex)); } }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_CS_VIDEO)){ // Set capability cs_video capabilities.setCsVideoSupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_IMAGE_SHARING)){ // Set capability image sharing capabilities.setImageSharingSupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_VIDEO_SHARING)){ // Set capability video sharing capabilities.setVideoSharingSupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_IP_VOICE_CALL)){ // Set capability ip voice call capabilities.setIPVoiceCallSupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_IP_VIDEO_CALL)){ // Set capability ip video call capabilities.setIPVideoCallSupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_IM_SESSION)){ // Set capability IM session capabilities.setImSessionSupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_FILE_TRANSFER)){ // Set capability file transfer capabilities.setFileTransferSupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_PRESENCE_DISCOVERY)){ // Set capability presence discovery capabilities.setPresenceDiscoverySupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_SOCIAL_PRESENCE)){ // Set capability social presence capabilities.setSocialPresenceSupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_GEOLOCATION_PUSH)){ // Set capability geoloc push capabilities.setGeolocationPushSupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_FILE_TRANSFER_THUMBNAIL)){ // Set capability file transfer thumbnail capabilities.setFileTransferThumbnailSupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_FILE_TRANSFER_HTTP)){ // Set capability file transfer HTTP capabilities.setFileTransferHttpSupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_FILE_TRANSFER_SF)){ // Set capability file transfer S&F capabilities.setFileTransferStoreForwardSupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_GROUP_CHAT_SF)){ // Set capability group chat S&F capabilities.setGroupChatStoreForwardSupport(true); }else if (mimeType.equalsIgnoreCase(MIMETYPE_CAPABILITY_EXTENSIONS)){ // Set RCS extensions capability int columnIndex = cursor.getColumnIndex(Data.DATA2); if (columnIndex!=-1){ String extensions = cursor.getString(columnIndex); String[] extensionList = extensions.split(";"); for (int i=0;i<extensionList.length;i++){ if (extensionList[i].trim().length()>0){ capabilities.addSupportedExtension(extensionList[i]); } } } }else if (mimeType.equalsIgnoreCase(MIMETYPE_FREE_TEXT)){ // Set free text int columnIndex = cursor.getColumnIndex(Data.DATA2); if (columnIndex!=-1){ presenceInfo.setFreetext(cursor.getString(columnIndex)); } }else if (mimeType.equalsIgnoreCase(MIMETYPE_PRESENCE_STATUS)){ // Set presence status int columnIndex = cursor.getColumnIndex(Data.DATA2); if (columnIndex!=-1){ int presence = cursor.getInt(columnIndex); if (presence == PRESENCE_STATUS_ONLINE){ presenceInfo.setPresenceStatus(PresenceInfo.ONLINE); }else if (presence == PRESENCE_STATUS_OFFLINE){ presenceInfo.setPresenceStatus(PresenceInfo.OFFLINE); }else{ presenceInfo.setPresenceStatus(PresenceInfo.UNKNOWN); } } }else if (mimeType.equalsIgnoreCase(MIMETYPE_REGISTRATION_STATE)){ // Set registration state int columnIndex = cursor.getColumnIndex(Data.DATA2); if (columnIndex!=-1){ contactInfo.setRegistrationState(cursor.getInt(columnIndex)); } }else if (mimeType.equalsIgnoreCase(MIMETYPE_RCS_STATUS)){ // Set RCS status int columnIndex = cursor.getColumnIndex(Data.DATA2); if (columnIndex!=-1){ contactInfo.setRcsStatus(cursor.getInt(columnIndex)); } }else if (mimeType.equalsIgnoreCase(MIMETYPE_RCS_STATUS_TIMESTAMP)){ // Set RCS status timestamp int columnIndex = cursor.getColumnIndex(Data.DATA2); if (columnIndex!=-1){ contactInfo.setRcsStatusTimestamp(cursor.getLong(columnIndex)); } }else if (mimeType.equalsIgnoreCase(MIMETYPE_NUMBER)){ // Set contact int columnIndex = cursor.getColumnIndex(Data.DATA1); if (columnIndex!=-1){ contactInfo.setContact(cursor.getString(columnIndex)); } } } cursor.close(); PhotoIcon photoIcon = null; if (photoContent!=null){ Bitmap bmp = BitmapFactory.decodeByteArray(photoContent, 0, photoContent.length); if (bmp != null) { photoIcon = new PhotoIcon(photoContent, bmp.getWidth(), bmp.getHeight(), photoEtag); } } presenceInfo.setPhotoIcon(photoIcon); contactInfo.setPresenceInfo(presenceInfo); contactInfo.setCapabilities(capabilities); return contactInfo; } /** * Utility to extract data from a raw contact. * * @param rawContactId the rawContactId * @return A cursor containing the requested data. */ private Cursor getRawContactDataCursor(final long rawContactId) { String[] projection = { Data._ID, Data.MIMETYPE, Data.DATA1, Data.DATA2, Website.URL, Photo.PHOTO }; // Filter the mime types String selection = "(" + Data.RAW_CONTACT_ID + " =?) AND (" + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=?)"; String[] selectionArgs = { Long.toString(rawContactId), MIMETYPE_WEBLINK, MIMETYPE_PHOTO, MIMETYPE_PHOTO_ETAG, MIMETYPE_RCS_STATUS, MIMETYPE_RCS_STATUS_TIMESTAMP, MIMETYPE_REGISTRATION_STATE, MIMETYPE_PRESENCE_STATUS, MIMETYPE_PRESENCE_TIMESTAMP, MIMETYPE_FREE_TEXT, MIMETYPE_NUMBER, MIMETYPE_CAPABILITY_TIMESTAMP, MIMETYPE_CAPABILITY_CS_VIDEO, MIMETYPE_CAPABILITY_IMAGE_SHARING, MIMETYPE_CAPABILITY_VIDEO_SHARING, MIMETYPE_CAPABILITY_IP_VOICE_CALL, MIMETYPE_CAPABILITY_IP_VIDEO_CALL, MIMETYPE_CAPABILITY_IM_SESSION, MIMETYPE_CAPABILITY_FILE_TRANSFER, MIMETYPE_CAPABILITY_PRESENCE_DISCOVERY, MIMETYPE_CAPABILITY_SOCIAL_PRESENCE, MIMETYPE_CAPABILITY_GEOLOCATION_PUSH, MIMETYPE_CAPABILITY_FILE_TRANSFER_THUMBNAIL, MIMETYPE_CAPABILITY_FILE_TRANSFER_HTTP, MIMETYPE_CAPABILITY_FILE_TRANSFER_SF, MIMETYPE_CAPABILITY_GROUP_CHAT_SF, MIMETYPE_CAPABILITY_EXTENSIONS }; Cursor cur = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, null); return cur; } /** * Update UI strings when device's locale has changed */ public void updateStrings(){ ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); // Update My profile display name ContentValues values = new ContentValues(); values.put(StructuredName.DISPLAY_NAME, ctx.getString(R.string.rcs_core_my_profile)); ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection("(" + Data.RAW_CONTACT_ID + " =?) AND (" + Data.MIMETYPE + "=?)", new String[]{Long.toString(getRawContactIdForMe()), StructuredName.DISPLAY_NAME}) .withValues(values) .build()); // Update file transfer menu values.clear(); values.put(Data.DATA2, getMimeTypeDescription(MIMETYPE_CAPABILITY_FILE_TRANSFER)); ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.MIMETYPE + "=?", new String[]{MIMETYPE_CAPABILITY_FILE_TRANSFER}) .withValues(values) .build()); // Update chat menu values.clear(); values.put(Data.DATA2, getMimeTypeDescription(MIMETYPE_CAPABILITY_IM_SESSION)); ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.MIMETYPE + "=?", new String[]{MIMETYPE_CAPABILITY_IM_SESSION}) .withValues(values) .build()); // Update image sharing menu values.clear(); values.put(Data.DATA2, getMimeTypeDescription(MIMETYPE_CAPABILITY_IMAGE_SHARING)); ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.MIMETYPE + "=?", new String[]{MIMETYPE_CAPABILITY_IMAGE_SHARING}) .withValues(values) .build()); // Update video sharing menu values.clear(); values.put(Data.DATA2, getMimeTypeDescription(MIMETYPE_CAPABILITY_VIDEO_SHARING)); ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.MIMETYPE + "=?", new String[]{MIMETYPE_CAPABILITY_VIDEO_SHARING}) .withValues(values) .build()); // Update ip voice call menu values.clear(); values.put(Data.DATA2, getMimeTypeDescription(MIMETYPE_CAPABILITY_IP_VOICE_CALL)); ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.MIMETYPE + "=?", new String[]{MIMETYPE_CAPABILITY_IP_VOICE_CALL}) .withValues(values) .build()); // Update ip video call menu values.clear(); values.put(Data.DATA2, getMimeTypeDescription(MIMETYPE_CAPABILITY_IP_VIDEO_CALL)); ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.MIMETYPE + "=?", new String[]{MIMETYPE_CAPABILITY_IP_VIDEO_CALL}) .withValues(values) .build()); // Update CS video menu values.clear(); values.put(Data.DATA2, getMimeTypeDescription(MIMETYPE_CAPABILITY_CS_VIDEO)); ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.MIMETYPE + "=?", new String[]{MIMETYPE_CAPABILITY_CS_VIDEO}) .withValues(values) .build()); // Update extensions menu values.clear(); values.put(Data.DATA2, getMimeTypeDescription(MIMETYPE_CAPABILITY_COMMON_EXTENSION)); ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI) .withSelection(Data.MIMETYPE + "=?", new String[]{MIMETYPE_CAPABILITY_COMMON_EXTENSION}) .withValues(values) .build()); if (!ops.isEmpty()){ // Do the actual database modifications try { ctx.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (RemoteException e) { if (logger.isActivated()){ logger.error("Something went wrong when updating the database strings",e); } } catch (OperationApplicationException e) { if (logger.isActivated()){ logger.error("Something went wrong when updating the database strings",e); } } } } /** * Clean the RCS entries * * <br>This removes the RCS entries that are associated to numbers not present in the address book anymore * <br>This also creates a RCS raw contact for numbers that are present, have RCS raw contact but not on all raw contacts * (typical example: a RCS number is present in the address book and another contact is created using the same number) */ public void cleanRCSEntries() { cleanRCSRawContactsInAB(); cleanEntriesInRichAB(); } /** * Clean AB */ private void cleanRCSRawContactsInAB() { // Get all RCS raw contacts id String[] projection = { Data.RAW_CONTACT_ID, Data.DATA1 }; String selection = Data.MIMETYPE + "=?"; String[] selectionArgs = { MIMETYPE_NUMBER }; Cursor cursor = ctx.getContentResolver().query(Data.CONTENT_URI, projection, selection, selectionArgs, null); // Delete RCS Entry where number is not in the address book anymore ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); // Changed by Deutsche Telekom if (cursor == null) { return; } while (cursor.moveToNext()) { long rawContactId = cursor.getLong(0); String phoneNumber = cursor.getString(1); if (getRawContactIdsFromPhoneNumber(phoneNumber).isEmpty()) { ops.add(ContentProviderOperation.newDelete(RawContacts.CONTENT_URI) .withSelection(RawContacts._ID + "=?", new String[]{Long.toString(rawContactId)}) .build()); // Also delete the corresponding entries in the aggregation provider ctx.getContentResolver().delete(AggregationData.CONTENT_URI, AggregationData.KEY_RCS_RAW_CONTACT_ID + "=?", new String[]{Long.toString(rawContactId)}); } } cursor.close(); if (!ops.isEmpty()){ // Do the actual database modifications try { ctx.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (RemoteException e) { if (logger.isActivated()){ logger.error("Something went wrong when updating the database strings",e); } } catch (OperationApplicationException e) { if (logger.isActivated()){ logger.error("Something went wrong when updating the database strings",e); } } } } /** * Clean EAB */ private void cleanEntriesInRichAB() { // Get All contact in EAB String[] projection = { RichAddressBookData.KEY_CONTACT_NUMBER }; Cursor cursor = null; try { cursor = ctx.getContentResolver().query(RichAddressBookData.CONTENT_URI, projection, null, null, null); // Changed by Deutsche Telekom if (cursor == null) { return; } // Delete EAB Entry where number is not in the address book anymore while (cursor.moveToNext()) { String phoneNumber = cursor.getString(0); if (getRawContactIdsFromPhoneNumber(phoneNumber).isEmpty()) { String where = RichAddressBookData.KEY_CONTACT_NUMBER + "=?"; String[] selectionArg = {phoneNumber}; ctx.getContentResolver().delete(RichAddressBookData.CONTENT_URI, where, selectionArg); } } cursor.close(); } catch (Exception e) { if (logger.isActivated()) { logger.error("Clean entries has failed", e); } } } /** * Get list of supported MIME types associated to RCS contacts * * @return MIME types */ public String[] getRcsMimeTypes(){ return new String[] { MIMETYPE_NUMBER, MIMETYPE_RCS_STATUS, MIMETYPE_REGISTRATION_STATE, MIMETYPE_RCS_STATUS_TIMESTAMP, MIMETYPE_PRESENCE_STATUS, MIMETYPE_FREE_TEXT, MIMETYPE_WEBLINK, MIMETYPE_PHOTO_ETAG, MIMETYPE_PRESENCE_TIMESTAMP, MIMETYPE_CAPABILITY_TIMESTAMP, MIMETYPE_CAPABILITY_CS_VIDEO, MIMETYPE_CAPABILITY_IMAGE_SHARING, MIMETYPE_CAPABILITY_VIDEO_SHARING, MIMETYPE_CAPABILITY_IP_VOICE_CALL, MIMETYPE_CAPABILITY_IP_VIDEO_CALL, MIMETYPE_CAPABILITY_IM_SESSION, MIMETYPE_CAPABILITY_FILE_TRANSFER, MIMETYPE_CAPABILITY_PRESENCE_DISCOVERY, MIMETYPE_CAPABILITY_SOCIAL_PRESENCE, MIMETYPE_CAPABILITY_GEOLOCATION_PUSH, MIMETYPE_CAPABILITY_FILE_TRANSFER_THUMBNAIL, MIMETYPE_CAPABILITY_FILE_TRANSFER_HTTP, MIMETYPE_CAPABILITY_FILE_TRANSFER_SF, MIMETYPE_CAPABILITY_GROUP_CHAT_SF, MIMETYPE_CAPABILITY_EXTENSIONS, MIMETYPE_SEE_MY_PROFILE, MIMETYPE_RCS_CONTACT, MIMETYPE_RCS_CAPABLE_CONTACT, MIMETYPE_NOT_RCS_CONTACT, MIMETYPE_IM_BLOCKED }; } /** * Delete all RCS entries in databases */ public void deleteRCSEntries() { // Delete Aggregation data ctx.getContentResolver().delete(AggregationData.CONTENT_URI, null, null); // Delete presence data ctx.getContentResolver().delete(RichAddressBookData.CONTENT_URI, null, null); } /** * Get the vCard file associated to a contact * * @param uri Contact URI in database * @return vCard filename */ public String getVisitCard(Uri uri) { String fileName = null; Cursor cursor = ctx.getContentResolver().query(uri, null, null, null, null); // Changed by Deutsche Telekom if (cursor == null) { return null; } while(cursor.moveToNext()) { String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); String lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)); Uri vCardUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey); AssetFileDescriptor fd; // Changed by Deutsche Telekom FileInputStream fis = null; FileOutputStream mFileOutputStream = null; try { fd = ctx.getContentResolver().openAssetFileDescriptor(vCardUri, "r"); // Changed by Deutsche Telekom fis = fd.createInputStream(); byte[] buf = new byte[(int) fd.getDeclaredLength()]; fis.read(buf); String Vcard = new String(buf); fileName = RcsSettings.getInstance().getFileRootDirectory() + File.separator + name + ".vcf"; File vCardFile = new File(fileName); if (vCardFile.exists()) vCardFile.delete(); mFileOutputStream = new FileOutputStream(vCardFile, true); mFileOutputStream.write(Vcard.toString().getBytes()); } catch (Exception e) { if (logger.isActivated()){ logger.error("Something went wrong when during creation of vcard",e); } } finally { CloseableUtils.close(fis); CloseableUtils.close(mFileOutputStream); } } cursor.close(); return fileName; } }