ContactSaveService.java revision cd321f65f1e50409812976380ad1f0fdb3fa35cb
1173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann/* 2173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * Copyright (C) 2010 The Android Open Source Project 3173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * 4173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * Licensed under the Apache License, Version 2.0 (the "License"); 5173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * you may not use this file except in compliance with the License. 6173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * You may obtain a copy of the License at 7173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * 8173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * http://www.apache.org/licenses/LICENSE-2.0 9173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * 10173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * Unless required by applicable law or agreed to in writing, software 11173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * distributed under the License is distributed on an "AS IS" BASIS, 12173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * See the License for the specific language governing permissions and 14173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * limitations under the License. 15173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann */ 16173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 1718ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikovpackage com.android.contacts; 18173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 193a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikovimport android.app.Activity; 20173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.app.IntentService; 21173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.content.ContentProviderOperation; 222b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.content.ContentProviderOperation.Builder; 23caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentProviderResult; 24caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentResolver; 25e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikovimport android.content.ContentUris; 26caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentValues; 279d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikovimport android.content.Context; 28173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.content.Intent; 292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.content.OperationApplicationException; 302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.database.Cursor; 31caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.net.Uri; 32c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport android.os.Bundle; 33886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikovimport android.os.Handler; 34886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikovimport android.os.Looper; 35a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport android.os.Parcelable; 362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.os.RemoteException; 37173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.provider.ContactsContract; 382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.provider.ContactsContract.AggregationExceptions; 391ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.GroupMembership; 409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikovimport android.provider.ContactsContract.Contacts; 41caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.Data; 42e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikovimport android.provider.ContactsContract.Groups; 43e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Leeimport android.provider.ContactsContract.PinnedPositions; 44ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelsonimport android.provider.ContactsContract.Profile; 45caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.RawContacts; 46c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoroimport android.provider.ContactsContract.RawContactsEntity; 47173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.util.Log; 482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.widget.Toast; 49173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 50d7ca03e23948c3b2d5f97ec5598d8b50e3fc0b25Chiao Chengimport com.android.contacts.common.database.ContactUpdateUtils; 510d5588da244d0992c3ff8f25d0875fdf95a8c644Chiao Chengimport com.android.contacts.common.model.AccountTypeManager; 52cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactDelta; 53cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactDeltaList; 54cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactModifier; 55428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Chengimport com.android.contacts.common.model.account.AccountWithDataSet; 56e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.contacts.util.CallerInfoCacheUtils; 57637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport com.android.contacts.util.ContactPhotoUtils; 58637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee 59e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Lists; 60e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Sets; 61e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Cheng 62e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargusimport java.io.File; 63e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargusimport java.io.FileInputStream; 64e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargusimport java.io.FileOutputStream; 65e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargusimport java.io.IOException; 66637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport java.io.InputStream; 67c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.ArrayList; 68c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.HashSet; 69c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.List; 70c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.concurrent.CopyOnWriteArrayList; 71173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 7218ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov/** 7318ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov * A service responsible for saving changes to the content provider. 7418ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov */ 75173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannpublic class ContactSaveService extends IntentService { 76173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann private static final String TAG = "ContactSaveService"; 77173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 78a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan /** Set to true in order to view logs on content provider operations */ 79a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan private static final boolean DEBUG = false; 80a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan 81caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String ACTION_NEW_RAW_CONTACT = "newRawContact"; 82caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 83caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_ACCOUNT_NAME = "accountName"; 84caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_ACCOUNT_TYPE = "accountType"; 852b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro public static final String EXTRA_DATA_SET = "dataSet"; 86caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_CONTENT_VALUES = "contentValues"; 87caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_CALLBACK_INTENT = "callbackIntent"; 88caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 89a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String ACTION_SAVE_CONTACT = "saveContact"; 90a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String EXTRA_CONTACT_STATE = "state"; 91a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String EXTRA_SAVE_MODE = "saveMode"; 92ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson public static final String EXTRA_SAVE_IS_PROFILE = "saveIsProfile"; 9336d24d7ede42a252c82c4aa783b2231c5e2eea79Dave Santoro public static final String EXTRA_SAVE_SUCCEEDED = "saveSucceeded"; 94e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus public static final String EXTRA_UPDATED_PHOTOS = "updatedPhotos"; 95173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 961ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov public static final String ACTION_CREATE_GROUP = "createGroup"; 97e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String ACTION_RENAME_GROUP = "renameGroup"; 98e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String ACTION_DELETE_GROUP = "deleteGroup"; 992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String ACTION_UPDATE_GROUP = "updateGroup"; 100e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String EXTRA_GROUP_ID = "groupId"; 101e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String EXTRA_GROUP_LABEL = "groupLabel"; 1022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String EXTRA_RAW_CONTACTS_TO_ADD = "rawContactsToAdd"; 1032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String EXTRA_RAW_CONTACTS_TO_REMOVE = "rawContactsToRemove"; 104e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 1059d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String ACTION_SET_STARRED = "setStarred"; 1067d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov public static final String ACTION_DELETE_CONTACT = "delete"; 1079d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String EXTRA_CONTACT_URI = "contactUri"; 1089d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String EXTRA_STARRED_FLAG = "starred"; 1099d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 1100f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary"; 1110f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String ACTION_CLEAR_PRIMARY = "clearPrimary"; 1120f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String EXTRA_DATA_ID = "dataId"; 1130f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 1142b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String ACTION_JOIN_CONTACTS = "joinContacts"; 1152b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String EXTRA_CONTACT_ID1 = "contactId1"; 1162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String EXTRA_CONTACT_ID2 = "contactId2"; 1172b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String EXTRA_CONTACT_WRITABLE = "contactWritable"; 1182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 119683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String ACTION_SET_SEND_TO_VOICEMAIL = "sendToVoicemail"; 120683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String EXTRA_SEND_TO_VOICEMAIL_FLAG = "sendToVoicemailFlag"; 121683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 122683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String ACTION_SET_RINGTONE = "setRingtone"; 123683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String EXTRA_CUSTOM_RINGTONE = "customRingtone"; 124683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 125caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet( 126caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.MIMETYPE, 127caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.IS_PRIMARY, 128caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA1, 129caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA2, 130caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA3, 131caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA4, 132caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA5, 133caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA6, 134caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA7, 135caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA8, 136caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA9, 137caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA10, 138caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA11, 139caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA12, 140caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA13, 141caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA14, 142caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA15 143caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ); 144caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 145a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov private static final int PERSIST_TRIES = 3; 146a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 1473a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public interface Listener { 1483a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public void onServiceCompleted(Intent callbackIntent); 1493a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1503a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 151a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson private static final CopyOnWriteArrayList<Listener> sListeners = 152a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson new CopyOnWriteArrayList<Listener>(); 1533a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1543a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov private Handler mMainHandler; 1553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 156173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann public ContactSaveService() { 157173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann super(TAG); 158173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann setIntentRedelivery(true); 1593a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler = new Handler(Looper.getMainLooper()); 1603a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1613a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1623a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public static void registerListener(Listener listener) { 1633a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov if (!(listener instanceof Activity)) { 1643a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov throw new ClassCastException("Only activities can be registered to" 1653a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov + " receive callback from " + ContactSaveService.class.getName()); 1663a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 167a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson sListeners.add(0, listener); 1683a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1693a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1703a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public static void unregisterListener(Listener listener) { 171a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson sListeners.remove(listener); 172173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann } 173173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 174173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann @Override 175a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public Object getSystemService(String name) { 176a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Object service = super.getSystemService(name); 177a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (service != null) { 178a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return service; 179a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 180a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 181a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return getApplicationContext().getSystemService(name); 182a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 183a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 184a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov @Override 185173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann protected void onHandleIntent(Intent intent) { 1862f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa // Call an appropriate method. If we're sure it affects how incoming phone calls are 1872f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa // handled, then notify the fact to in-call screen. 188caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String action = intent.getAction(); 189caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov if (ACTION_NEW_RAW_CONTACT.equals(action)) { 190caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov createRawContact(intent); 1912f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this); 192a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } else if (ACTION_SAVE_CONTACT.equals(action)) { 193a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov saveContact(intent); 1942f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this); 1951ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } else if (ACTION_CREATE_GROUP.equals(action)) { 1961ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov createGroup(intent); 197e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } else if (ACTION_RENAME_GROUP.equals(action)) { 198e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov renameGroup(intent); 199e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } else if (ACTION_DELETE_GROUP.equals(action)) { 200e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov deleteGroup(intent); 2012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } else if (ACTION_UPDATE_GROUP.equals(action)) { 2022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan updateGroup(intent); 2039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } else if (ACTION_SET_STARRED.equals(action)) { 2049d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov setStarred(intent); 2050f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) { 2060f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann setSuperPrimary(intent); 2070f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } else if (ACTION_CLEAR_PRIMARY.equals(action)) { 2080f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann clearPrimary(intent); 2097d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } else if (ACTION_DELETE_CONTACT.equals(action)) { 2107d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov deleteContact(intent); 2112f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this); 2122b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } else if (ACTION_JOIN_CONTACTS.equals(action)) { 2132b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov joinContacts(intent); 2142f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this); 215683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) { 216683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson setSendToVoicemail(intent); 2172f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this); 218683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } else if (ACTION_SET_RINGTONE.equals(action)) { 219683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson setRingtone(intent); 2202f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this); 221caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 222caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 223caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 2249d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov /** 2259d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to create a new raw contact 2269d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * using data presented as a set of ContentValues. 2279d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov */ 2289d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createNewRawContactIntent(Context context, 2292b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro ArrayList<ContentValues> values, AccountWithDataSet account, 230e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 2319d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent( 2329d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov context, ContactSaveService.class); 2339d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT); 2349d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov if (account != null) { 2359d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name); 2369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type); 2372b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet); 2389d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 2399d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putParcelableArrayListExtra( 2409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov ContactSaveService.EXTRA_CONTENT_VALUES, values); 2419d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 2429d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // Callback intent will be invoked by the service once the new contact is 2439d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // created. The service will put the URI of the new contact as "data" on 2449d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // the callback intent. 2459d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 2469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov callbackIntent.setAction(callbackAction); 2479d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 2489d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov return serviceIntent; 2499d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 2509d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 251caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov private void createRawContact(Intent intent) { 252caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME); 253caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE); 2542b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String dataSet = intent.getStringExtra(EXTRA_DATA_SET); 255caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES); 256caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 257caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 258caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); 259caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) 260caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValue(RawContacts.ACCOUNT_NAME, accountName) 261caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValue(RawContacts.ACCOUNT_TYPE, accountType) 2622b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro .withValue(RawContacts.DATA_SET, dataSet) 263caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .build()); 264caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 265caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov int size = valueList.size(); 266caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov for (int i = 0; i < size; i++) { 267caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentValues values = valueList.get(i); 268caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov values.keySet().retainAll(ALLOWED_DATA_COLUMNS); 269caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) 270caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValueBackReference(Data.RAW_CONTACT_ID, 0) 271caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValues(values) 272caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .build()); 273caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 274caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 275caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentResolver resolver = getContentResolver(); 276caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentProviderResult[] results; 277caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov try { 278caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov results = resolver.applyBatch(ContactsContract.AUTHORITY, operations); 279caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } catch (Exception e) { 280caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov throw new RuntimeException("Failed to store new contact", e); 281caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 282caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 283caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Uri rawContactUri = results[0].uri; 284caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri)); 285caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 2863a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 287caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 288caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 289caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov /** 290a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov * Creates an intent that can be sent to this service to create a new raw contact 291a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov * using data presented as a set of ContentValues. 292e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * This variant is more convenient to use when there is only one photo that can 293e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * possibly be updated, as in the Contact Details screen. 294e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @param rawContactId identifies a writable raw-contact whose photo is to be updated. 295e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @param updatedPhotoPath denotes a temporary file containing the contact's new photo. 296a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov */ 297851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu public static Intent createSaveContactIntent(Context context, RawContactDeltaList state, 298e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus String saveModeExtraKey, int saveMode, boolean isProfile, 299e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction, long rawContactId, 300637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee Uri updatedPhotoPath) { 301e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus Bundle bundle = new Bundle(); 302637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee bundle.putParcelable(String.valueOf(rawContactId), updatedPhotoPath); 303e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus return createSaveContactIntent(context, state, saveModeExtraKey, saveMode, isProfile, 304e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus callbackActivity, callbackAction, bundle); 305e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 306e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 307e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus /** 308e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * Creates an intent that can be sent to this service to create a new raw contact 309e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * using data presented as a set of ContentValues. 310e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * This variant is used when multiple contacts' photos may be updated, as in the 311e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * Contact Editor. 312e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @param updatedPhotos maps each raw-contact's ID to the file-path of the new photo. 313e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus */ 314851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu public static Intent createSaveContactIntent(Context context, RawContactDeltaList state, 315e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus String saveModeExtraKey, int saveMode, boolean isProfile, 316e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction, 317e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Bundle updatedPhotos) { 318a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Intent serviceIntent = new Intent( 319a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov context, ContactSaveService.class); 320a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT); 321a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state); 322ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson serviceIntent.putExtra(EXTRA_SAVE_IS_PROFILE, isProfile); 323e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus if (updatedPhotos != null) { 324e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus serviceIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos); 325e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 326a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 327e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus if (callbackActivity != null) { 328e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // Callback intent will be invoked by the service once the contact is 329e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // saved. The service will put the URI of the new contact as "data" on 330e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // the callback intent. 331e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Intent callbackIntent = new Intent(context, callbackActivity); 332e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.putExtra(saveModeExtraKey, saveMode); 333e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.setAction(callbackAction); 334e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 335e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus } 336a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return serviceIntent; 337a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 338a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 339a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov private void saveContact(Intent intent) { 340851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu RawContactDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE); 341ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false); 342e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus Bundle updatedPhotos = intent.getParcelableExtra(EXTRA_UPDATED_PHOTOS); 343a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 344a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Trim any empty fields, and RawContacts, before persisting 345a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this); 346851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu RawContactModifier.trimEmpty(state, accountTypes); 347a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 348a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Uri lookupUri = null; 349a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 350a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ContentResolver resolver = getContentResolver(); 351e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus boolean succeeded = false; 352a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 353ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // Keep track of the id of a newly raw-contact (if any... there can be at most one). 354ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus long insertedRawContactId = -1; 355ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 356a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Attempt to persist changes 357a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov int tries = 0; 358a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov while (tries++ < PERSIST_TRIES) { 359a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov try { 360a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Build operations and try applying 361a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ArrayList<ContentProviderOperation> diff = state.buildDiff(); 362a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan if (DEBUG) { 363a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan Log.v(TAG, "Content Provider Operations:"); 364a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan for (ContentProviderOperation operation : diff) { 365a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan Log.v(TAG, operation.toString()); 366a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan } 367a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan } 368a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan 369a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov ContentProviderResult[] results = null; 370a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (!diff.isEmpty()) { 371a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov results = resolver.applyBatch(ContactsContract.AUTHORITY, diff); 372a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 373a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 374a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final long rawContactId = getRawContactId(state, diff, results); 375a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (rawContactId == -1) { 376a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov throw new IllegalStateException("Could not determine RawContact ID after save"); 377a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 378ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // We don't have to check to see if the value is still -1. If we reach here, 379ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // the previous loop iteration didn't succeed, so any ID that we obtained is bogus. 380ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus insertedRawContactId = getInsertedRawContactId(diff, results); 3817c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro if (isProfile) { 3827c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro // Since the profile supports local raw contacts, which may have been completely 3837c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro // removed if all information was removed, we need to do a special query to 3847c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro // get the lookup URI for the profile contact (if it still exists). 3857c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro Cursor c = resolver.query(Profile.CONTENT_URI, 3867c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro new String[] {Contacts._ID, Contacts.LOOKUP_KEY}, 3877c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro null, null, null); 3887c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro try { 389162b7e34fb581d0fa279957af5136d190c40759fErik if (c.moveToFirst()) { 390162b7e34fb581d0fa279957af5136d190c40759fErik final long contactId = c.getLong(0); 391162b7e34fb581d0fa279957af5136d190c40759fErik final String lookupKey = c.getString(1); 392162b7e34fb581d0fa279957af5136d190c40759fErik lookupUri = Contacts.getLookupUri(contactId, lookupKey); 393162b7e34fb581d0fa279957af5136d190c40759fErik } 3947c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } finally { 3957c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro c.close(); 3967c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } 3977c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } else { 3987c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, 3997c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro rawContactId); 4007c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri); 4017c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } 402a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Log.v(TAG, "Saved contact. New URI: " + lookupUri); 403e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 404e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus // We can change this back to false later, if we fail to save the contact photo. 405e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus succeeded = true; 406a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov break; 407a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 408a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } catch (RemoteException e) { 409a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Something went wrong, bail without success 410a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Log.e(TAG, "Problem persisting user edits", e); 411a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov break; 412a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 413a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } catch (OperationApplicationException e) { 414a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Version consistency failed, re-parent change and try again 415a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString()); 416a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN("); 417a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov boolean first = true; 418a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final int count = state.size(); 419a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov for (int i = 0; i < count; i++) { 420a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Long rawContactId = state.getRawContactId(i); 421a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (rawContactId != null && rawContactId != -1) { 422a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (!first) { 423a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(','); 424a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 425a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(rawContactId); 426a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov first = false; 427a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 428a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 429a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(")"); 430a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 431a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (first) { 432a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov throw new IllegalStateException("Version consistency failed for a new contact"); 433a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 434a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 435851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu final RawContactDeltaList newState = RawContactDeltaList.fromQuery( 436c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro isProfile 437c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro ? RawContactsEntity.PROFILE_CONTENT_URI 438c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro : RawContactsEntity.CONTENT_URI, 439c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro resolver, sb.toString(), null, null); 440851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu state = RawContactDeltaList.mergeAfter(newState, state); 441c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro 442c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro // Update the new state to use profile URIs if appropriate. 443c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro if (isProfile) { 444851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu for (RawContactDelta delta : state) { 445c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro delta.setProfileQueryUri(); 446c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro } 447c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro } 448a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 449a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 450a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 451e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus // Now save any updated photos. We do this at the end to ensure that 452e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus // the ContactProvider already knows about newly-created contacts. 453e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus if (updatedPhotos != null) { 454e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus for (String key : updatedPhotos.keySet()) { 455637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee Uri photoUri = updatedPhotos.getParcelable(key); 456e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus long rawContactId = Long.parseLong(key); 457ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 458ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // If the raw-contact ID is negative, we are saving a new raw-contact; 459ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // replace the bogus ID with the new one that we actually saved the contact at. 460ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus if (rawContactId < 0) { 461ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus rawContactId = insertedRawContactId; 462ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus if (rawContactId == -1) { 463ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus throw new IllegalStateException( 464ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus "Could not determine RawContact ID for image insertion"); 465ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } 466ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } 467ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 468637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee if (!saveUpdatedPhoto(rawContactId, photoUri)) succeeded = false; 469e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 470e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 471e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 472e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 473e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus if (callbackIntent != null) { 474e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus if (succeeded) { 475e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // Mark the intent to indicate that the save was successful (even if the lookup URI 476e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // is now null). For local contacts or the local profile, it's possible that the 477e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // save triggered removal of the contact, so no lookup URI would exist.. 478e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.putExtra(EXTRA_SAVE_SUCCEEDED, true); 479e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus } 480e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.setData(lookupUri); 481e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus deliverCallback(callbackIntent); 482e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 483a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 484a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 485e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus /** 486e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * Save updated photo for the specified raw-contact. 487e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @return true for success, false for failure 488e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus */ 489637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee private boolean saveUpdatedPhoto(long rawContactId, Uri photoUri) { 490ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final Uri outputUri = Uri.withAppendedPath( 491e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), 492e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus RawContacts.DisplayPhoto.CONTENT_DIRECTORY); 493e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 494637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee return ContactPhotoUtils.savePhotoFromUriToUri(this, photoUri, outputUri, true); 495e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 496e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 497ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus /** 498ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus * Find the ID of an existing or newly-inserted raw-contact. If none exists, return -1. 499ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus */ 500851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu private long getRawContactId(RawContactDeltaList state, 501a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ArrayList<ContentProviderOperation> diff, 502a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ContentProviderResult[] results) { 503ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus long existingRawContactId = state.findRawContactId(); 504ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus if (existingRawContactId != -1) { 505ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus return existingRawContactId; 506a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 507a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 508ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus return getInsertedRawContactId(diff, results); 509ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } 510ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 511ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus /** 512ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus * Find the ID of a newly-inserted raw-contact. If none exists, return -1. 513ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus */ 514ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus private long getInsertedRawContactId( 515ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final ArrayList<ContentProviderOperation> diff, 516ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final ContentProviderResult[] results) { 517a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final int diffSize = diff.size(); 518a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov for (int i = 0; i < diffSize; i++) { 519a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov ContentProviderOperation operation = diff.get(i); 520a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (operation.getType() == ContentProviderOperation.TYPE_INSERT 521a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov && operation.getUri().getEncodedPath().contains( 522a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov RawContacts.CONTENT_URI.getEncodedPath())) { 523a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return ContentUris.parseId(results[i].uri); 524a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 525a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 526a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return -1; 527a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 528a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 529a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov /** 530717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * Creates an intent that can be sent to this service to create a new group as 531717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * well as add new members at the same time. 532717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * 533717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param context of the application 534717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param account in which the group should be created 535717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param label is the name of the group (cannot be null) 536717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param rawContactsToAdd is an array of raw contact IDs for contacts that 537717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * should be added to the group 538717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param callbackActivity is the activity to send the callback intent to 539717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param callbackAction is the intent action for the callback intent 540caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov */ 5412b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro public static Intent createNewGroupIntent(Context context, AccountWithDataSet account, 542e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus String label, long[] rawContactsToAdd, Class<? extends Activity> callbackActivity, 543717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan String callbackAction) { 5449d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 5459d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP); 5469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type); 5479d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name); 5482b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet); 5499d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label); 550717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd); 551caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 5529d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // Callback intent will be invoked by the service once the new group is 553717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // created. 5549d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 5559d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov callbackIntent.setAction(callbackAction); 5561ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 5579d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 5581ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov return serviceIntent; 5591ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 5601ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 5611ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov private void createGroup(Intent intent) { 5622b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE); 5632b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME); 5642b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String dataSet = intent.getStringExtra(EXTRA_DATA_SET); 5652b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 566717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD); 5671ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 5681ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov ContentValues values = new ContentValues(); 5691ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.ACCOUNT_TYPE, accountType); 5701ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.ACCOUNT_NAME, accountName); 5712b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro values.put(Groups.DATA_SET, dataSet); 5721ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.TITLE, label); 5731ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 574717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final ContentResolver resolver = getContentResolver(); 575717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 576717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Create the new group 577717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values); 578717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 579717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // If there's no URI, then the insertion failed. Abort early because group members can't be 580717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // added if the group doesn't exist 5811ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov if (groupUri == null) { 582717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan Log.e(TAG, "Couldn't create group with label " + label); 5831ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov return; 5841ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 5851ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 586717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Add new group members 587717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri)); 588717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 589717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // TODO: Move this into the contact editor where it belongs. This needs to be integrated 590717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // with the way other intent extras that are passed to the {@link ContactEditorActivity}. 5911ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.clear(); 5921ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 5931ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri)); 5941ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 5951ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 596c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setData(groupUri); 597717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // TODO: This can be taken out when the above TODO is addressed 5981ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values)); 5993a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 6001ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 6011ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 6021ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov /** 6039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to rename a group. 6041ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov */ 605c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel, 606e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 6079d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 6089d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP); 6099d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 6109d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel); 611c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 612c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan // Callback intent will be invoked by the service once the group is renamed. 613c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan Intent callbackIntent = new Intent(context, callbackActivity); 614c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setAction(callbackAction); 615c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 616c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 617caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov return serviceIntent; 618caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 619e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 620e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov private void renameGroup(Intent intent) { 621e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 622e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 623e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 624e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov if (groupId == -1) { 625e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov Log.e(TAG, "Invalid arguments for renameGroup request"); 626e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return; 627e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 628e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 629e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov ContentValues values = new ContentValues(); 630e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov values.put(Groups.TITLE, label); 631c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId); 632c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan getContentResolver().update(groupUri, values, null, null); 633c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 634c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 635c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setData(groupUri); 636c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan deliverCallback(callbackIntent); 637e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 638e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 639e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov /** 6409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to delete a group. 641e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov */ 6429d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createGroupDeletionIntent(Context context, long groupId) { 6439d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 6449d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP); 645e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 646e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return serviceIntent; 647e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 648e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 649e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov private void deleteGroup(Intent intent) { 650e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 651e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov if (groupId == -1) { 652e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov Log.e(TAG, "Invalid arguments for deleteGroup request"); 653e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return; 654e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 655e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 656e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov getContentResolver().delete( 657e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null); 658e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 659e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 660e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov /** 6612d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * Creates an intent that can be sent to this service to rename a group as 6622d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * well as add and remove members from the group. 6632d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * 6642d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param context of the application 6652d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param groupId of the group that should be modified 6662d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param newLabel is the updated name of the group (can be null if the name 6672d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * should not be updated) 6682d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param rawContactsToAdd is an array of raw contact IDs for contacts that 6692d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * should be added to the group 6702d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param rawContactsToRemove is an array of raw contact IDs for contacts 6712d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * that should be removed from the group 6722d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param callbackActivity is the activity to send the callback intent to 6732d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param callbackAction is the intent action for the callback intent 6742d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan */ 6752d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel, 6762d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToAdd, long[] rawContactsToRemove, 677e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 6782d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Intent serviceIntent = new Intent(context, ContactSaveService.class); 6792d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP); 6802d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 6812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel); 6822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd); 6832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_REMOVE, 6842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactsToRemove); 6852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 6862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Callback intent will be invoked by the service once the group is updated 6872d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Intent callbackIntent = new Intent(context, callbackActivity); 6882d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan callbackIntent.setAction(callbackAction); 6892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 6902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 6912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan return serviceIntent; 6922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 6932d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 6942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan private void updateGroup(Intent intent) { 6952d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 6962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 6972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD); 6982d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToRemove = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_REMOVE); 6992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (groupId == -1) { 7012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.e(TAG, "Invalid arguments for updateGroup request"); 7022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan return; 7032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7052d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentResolver resolver = getContentResolver(); 7062d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId); 7072d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7082d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Update group name if necessary 7092d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (label != null) { 7102d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan ContentValues values = new ContentValues(); 7112d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan values.put(Groups.TITLE, label); 712717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan resolver.update(groupUri, values, null, null); 7132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 715717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Add and remove members if necessary 716717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan addMembersToGroup(resolver, rawContactsToAdd, groupId); 717717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan removeMembersFromGroup(resolver, rawContactsToRemove, groupId); 718717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 719717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 720717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan callbackIntent.setData(groupUri); 721717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan deliverCallback(callbackIntent); 722717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 723717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 72418958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann private static void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd, 725717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan long groupId) { 726717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan if (rawContactsToAdd == null) { 727717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan return; 728717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 7292d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (long rawContactId : rawContactsToAdd) { 7302d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan try { 7312d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ArrayList<ContentProviderOperation> rawContactOperations = 7322d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new ArrayList<ContentProviderOperation>(); 7332d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Build an assert operation to ensure the contact is not already in the group 7352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentProviderOperation.Builder assertBuilder = ContentProviderOperation 7362d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan .newAssertQuery(Data.CONTENT_URI); 7372d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan assertBuilder.withSelection(Data.RAW_CONTACT_ID + "=? AND " + 7382d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?", 7392d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new String[] { String.valueOf(rawContactId), 7402d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)}); 7412d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan assertBuilder.withExpectedCount(0); 7422d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactOperations.add(assertBuilder.build()); 7432d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7442d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Build an insert operation to add the contact to the group 7452d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentProviderOperation.Builder insertBuilder = ContentProviderOperation 7462d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan .newInsert(Data.CONTENT_URI); 7472d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(Data.RAW_CONTACT_ID, rawContactId); 7482d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 7492d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(GroupMembership.GROUP_ROW_ID, groupId); 7502d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactOperations.add(insertBuilder.build()); 7512d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7522d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (DEBUG) { 7532d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (ContentProviderOperation operation : rawContactOperations) { 7542d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.v(TAG, operation.toString()); 7552d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7562d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7572d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7582d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Apply batch 7592d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (!rawContactOperations.isEmpty()) { 76018958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations); 7612d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7622d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } catch (RemoteException e) { 7632d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Something went wrong, bail without success 7642d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.e(TAG, "Problem persisting user edits for raw contact ID " + 7652d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(rawContactId), e); 7662d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } catch (OperationApplicationException e) { 7672d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // The assert could have failed because the contact is already in the group, 7682d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // just continue to the next contact 7692d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.w(TAG, "Assert failed in adding raw contact ID " + 7702d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(rawContactId) + ". Already exists in group " + 7712d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(groupId), e); 7722d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7732d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 774717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 7752d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 77618958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann private static void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove, 777717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan long groupId) { 778717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan if (rawContactsToRemove == null) { 779717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan return; 780717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 7812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (long rawContactId : rawContactsToRemove) { 7822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Apply the delete operation on the data row for the given raw contact's 7832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // membership in the given group. If no contact matches the provided selection, then 7842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // nothing will be done. Just continue to the next contact. 78518958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann resolver.delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " + 7862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?", 7872d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new String[] { String.valueOf(rawContactId), 7882d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)}); 7892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan /** 7939d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to star or un-star a contact. 794e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov */ 7959d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) { 7969d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 7979d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED); 7989d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 7999d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value); 8009d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 801e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return serviceIntent; 802e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 8039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 8049d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov private void setStarred(Intent intent) { 8059d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 8069d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false); 8079d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov if (contactUri == null) { 8089d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Log.e(TAG, "Invalid arguments for setStarred request"); 8099d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov return; 8109d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 8119d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 8129d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov final ContentValues values = new ContentValues(1); 8139d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov values.put(Contacts.STARRED, value); 8149d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov getContentResolver().update(contactUri, values, null, null); 815e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee 816e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee // Undemote the contact if necessary 817e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee final Cursor c = getContentResolver().query(contactUri, new String[] {Contacts._ID}, 818e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee null, null, null); 819e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee try { 820e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee if (c.moveToFirst()) { 821e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee final long id = c.getLong(0); 822bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee 823bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee // Don't bother undemoting if this contact is the user's profile. 824bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee if (id < Profile.MIN_ID) { 825bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee values.clear(); 826bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee values.put(String.valueOf(id), PinnedPositions.UNDEMOTE); 827bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee getContentResolver().update(PinnedPositions.UPDATE_URI, values, null, null); 828bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee } 829e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee } 830e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee } finally { 831e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee c.close(); 832e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee } 8339d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 8340f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 8350f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann /** 836683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson * Creates an intent that can be sent to this service to set the redirect to voicemail. 837683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson */ 838683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static Intent createSetSendToVoicemail(Context context, Uri contactUri, 839683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson boolean value) { 840683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Intent serviceIntent = new Intent(context, ContactSaveService.class); 841683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.setAction(ContactSaveService.ACTION_SET_SEND_TO_VOICEMAIL); 842683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 843683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_SEND_TO_VOICEMAIL_FLAG, value); 844683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 845683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return serviceIntent; 846683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 847683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 848683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson private void setSendToVoicemail(Intent intent) { 849683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 850683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson boolean value = intent.getBooleanExtra(EXTRA_SEND_TO_VOICEMAIL_FLAG, false); 851683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson if (contactUri == null) { 852683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Log.e(TAG, "Invalid arguments for setRedirectToVoicemail"); 853683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return; 854683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 855683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 856683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson final ContentValues values = new ContentValues(1); 857683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson values.put(Contacts.SEND_TO_VOICEMAIL, value); 858683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson getContentResolver().update(contactUri, values, null, null); 859683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 860683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 861683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson /** 862683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson * Creates an intent that can be sent to this service to save the contact's ringtone. 863683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson */ 864683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static Intent createSetRingtone(Context context, Uri contactUri, 865683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson String value) { 866683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Intent serviceIntent = new Intent(context, ContactSaveService.class); 867683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.setAction(ContactSaveService.ACTION_SET_RINGTONE); 868683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 869683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CUSTOM_RINGTONE, value); 870683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 871683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return serviceIntent; 872683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 873683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 874683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson private void setRingtone(Intent intent) { 875683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 876683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson String value = intent.getStringExtra(EXTRA_CUSTOM_RINGTONE); 877683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson if (contactUri == null) { 878683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Log.e(TAG, "Invalid arguments for setRingtone"); 879683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return; 880683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 881683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson ContentValues values = new ContentValues(1); 882683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson values.put(Contacts.CUSTOM_RINGTONE, value); 883683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson getContentResolver().update(contactUri, values, null, null); 884683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 885683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 886683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson /** 8870f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * Creates an intent that sets the selected data item as super primary (default) 8880f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann */ 8890f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static Intent createSetSuperPrimaryIntent(Context context, long dataId) { 8900f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Intent serviceIntent = new Intent(context, ContactSaveService.class); 8910f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY); 8920f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId); 8930f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return serviceIntent; 8940f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 8950f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 8960f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann private void setSuperPrimary(Intent intent) { 8970f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1); 8980f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann if (dataId == -1) { 8990f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Log.e(TAG, "Invalid arguments for setSuperPrimary request"); 9000f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return; 9010f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9020f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 903d7ca03e23948c3b2d5f97ec5598d8b50e3fc0b25Chiao Cheng ContactUpdateUtils.setSuperPrimary(this, dataId); 9040f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9050f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9060f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann /** 9070f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * Creates an intent that clears the primary flag of all data items that belong to the same 9080f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * raw_contact as the given data item. Will only clear, if the data item was primary before 9090f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * this call 9100f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann */ 9110f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static Intent createClearPrimaryIntent(Context context, long dataId) { 9120f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Intent serviceIntent = new Intent(context, ContactSaveService.class); 9130f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY); 9140f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId); 9150f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return serviceIntent; 9160f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9170f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9180f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann private void clearPrimary(Intent intent) { 9190f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1); 9200f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann if (dataId == -1) { 9210f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Log.e(TAG, "Invalid arguments for clearPrimary request"); 9220f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return; 9230f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9240f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9250f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann // Update the primary values in the data record. 9260f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann ContentValues values = new ContentValues(1); 9270f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_SUPER_PRIMARY, 0); 9280f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_PRIMARY, 0); 9290f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9300f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), 9310f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values, null, null); 9320f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9337d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 9347d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov /** 9357d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov * Creates an intent that can be sent to this service to delete a contact. 9367d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov */ 9377d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov public static Intent createDeleteContactIntent(Context context, Uri contactUri) { 9387d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 9397d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT); 9407d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 9417d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov return serviceIntent; 9427d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 9437d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 9447d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov private void deleteContact(Intent intent) { 9457d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 9467d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov if (contactUri == null) { 9477d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Log.e(TAG, "Invalid arguments for deleteContact request"); 9487d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov return; 9497d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 9507d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 9517d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov getContentResolver().delete(contactUri, null, null); 9527d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 9532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov /** 9552b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov * Creates an intent that can be sent to this service to join two contacts. 9562b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov */ 9572b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static Intent createJoinContactsIntent(Context context, long contactId1, 9582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long contactId2, boolean contactWritable, 959e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 9602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 9612b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS); 9622b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1); 9632b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2); 9642b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_WRITABLE, contactWritable); 9652b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9662b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Callback intent will be invoked by the service once the contacts are joined. 9672b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 9682b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov callbackIntent.setAction(callbackAction); 9692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 9702b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9712b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov return serviceIntent; 9722b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 9732b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9742b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9752b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov private interface JoinContactQuery { 9762b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov String[] PROJECTION = { 9772b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts._ID, 9782b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts.CONTACT_ID, 9792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts.NAME_VERIFIED, 9802b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts.DISPLAY_NAME_SOURCE, 9812b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov }; 9822b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9832b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?"; 9842b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9852b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int _ID = 0; 9862b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int CONTACT_ID = 1; 9872b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int NAME_VERIFIED = 2; 9882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int DISPLAY_NAME_SOURCE = 3; 9892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 9902b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9912b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov private void joinContacts(Intent intent) { 9922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1); 9932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1); 9942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov boolean writable = intent.getBooleanExtra(EXTRA_CONTACT_WRITABLE, false); 9952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (contactId1 == -1 || contactId2 == -1) { 9962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Log.e(TAG, "Invalid arguments for joinContacts request"); 9972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov return; 9982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 9992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov final ContentResolver resolver = getContentResolver(); 10012b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10022b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Load raw contact IDs for all raw contacts involved - currently edited and selected 10032b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // in the join UIs 10042b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Cursor c = resolver.query(RawContacts.CONTENT_URI, 10052b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov JoinContactQuery.PROJECTION, 10062b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov JoinContactQuery.SELECTION, 10072b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov new String[]{String.valueOf(contactId1), String.valueOf(contactId2)}, null); 10082b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10092b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long rawContactIds[]; 10102b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long verifiedNameRawContactId = -1; 10112b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov try { 10127f42b90f870d8df1b12dc90162775ef5f3b31b85Jay Shrauner if (c.getCount() == 0) { 10137f42b90f870d8df1b12dc90162775ef5f3b31b85Jay Shrauner return; 10147f42b90f870d8df1b12dc90162775ef5f3b31b85Jay Shrauner } 10152b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int maxDisplayNameSource = -1; 10162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov rawContactIds = new long[c.getCount()]; 10172b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int i = 0; i < rawContactIds.length; i++) { 10182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov c.moveToPosition(i); 10192b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long rawContactId = c.getLong(JoinContactQuery._ID); 10202b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov rawContactIds[i] = rawContactId; 10212b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE); 10222b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (nameSource > maxDisplayNameSource) { 10232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov maxDisplayNameSource = nameSource; 10242b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10272b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Find an appropriate display name for the joined contact: 10282b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // if should have a higher DisplayNameSource or be the name 10292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // of the original contact that we are joining with another. 10302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (writable) { 10312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int i = 0; i < rawContactIds.length; i++) { 10322b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov c.moveToPosition(i); 10332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (c.getLong(JoinContactQuery.CONTACT_ID) == contactId1) { 10342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE); 10352b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (nameSource == maxDisplayNameSource 10362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov && (verifiedNameRawContactId == -1 10372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0)) { 10382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov verifiedNameRawContactId = c.getLong(JoinContactQuery._ID); 10392b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10402b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10412b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10422b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10432b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } finally { 10442b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov c.close(); 10452b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10462b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10472b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // For each pair of raw contacts, insert an aggregation exception 10482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); 10492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int i = 0; i < rawContactIds.length; i++) { 10502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int j = 0; j < rawContactIds.length; j++) { 10512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (i != j) { 10522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]); 10532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10552b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10562b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10572b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Mark the original contact as "name verified" to make sure that the contact 10582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // display name does not change as a result of the join 10592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (verifiedNameRawContactId != -1) { 10602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Builder builder = ContentProviderOperation.newUpdate( 10612b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId)); 10622b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(RawContacts.NAME_VERIFIED, 1); 10632b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov operations.add(builder.build()); 10642b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10652b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10662b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov boolean success = false; 10672b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Apply all aggregation exceptions as one batch 10682b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov try { 10692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov resolver.applyBatch(ContactsContract.AUTHORITY, operations); 1070886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov showToast(R.string.contactsJoinedMessage); 10712b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov success = true; 10722b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } catch (RemoteException e) { 10732b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Log.e(TAG, "Failed to apply aggregation exception batch", e); 1074886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov showToast(R.string.contactSavedErrorToast); 10752b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } catch (OperationApplicationException e) { 10762b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Log.e(TAG, "Failed to apply aggregation exception batch", e); 1077886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov showToast(R.string.contactSavedErrorToast); 10782b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10802b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 10812b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (success) { 10822b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Uri uri = RawContacts.getContactLookupUri(resolver, 10832b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0])); 10842b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov callbackIntent.setData(uri); 10852b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10863a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 10872b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov /** 10902b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation. 10912b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov */ 10922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations, 10932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long rawContactId1, long rawContactId2) { 10942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Builder builder = 10952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI); 10962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER); 10972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1); 10982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2); 10992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov operations.add(builder.build()); 11002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 1101886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov 1102886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov /** 1103886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov * Shows a toast on the UI thread. 1104886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov */ 1105886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov private void showToast(final int message) { 11063a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler.post(new Runnable() { 1107886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov 1108886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov @Override 1109886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov public void run() { 1110886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov Toast.makeText(ContactSaveService.this, message, Toast.LENGTH_LONG).show(); 1111886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov } 1112886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov }); 1113886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov } 11143a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 11153a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov private void deliverCallback(final Intent callbackIntent) { 11163a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler.post(new Runnable() { 11173a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 11183a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov @Override 11193a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public void run() { 11203a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallbackOnUiThread(callbackIntent); 11213a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 11223a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov }); 11233a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 11243a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 11253a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov void deliverCallbackOnUiThread(final Intent callbackIntent) { 11263a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // TODO: this assumes that if there are multiple instances of the same 11273a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // activity registered, the last one registered is the one waiting for 11283a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // the callback. Validity of this assumption needs to be verified. 1129a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson for (Listener listener : sListeners) { 1130a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson if (callbackIntent.getComponent().equals( 1131a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson ((Activity) listener).getIntent().getComponent())) { 1132a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson listener.onServiceCompleted(callbackIntent); 1133a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson return; 11343a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 11353a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 11363a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1137173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann} 1138