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; 43ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelsonimport android.provider.ContactsContract.Profile; 44caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.RawContacts; 45c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoroimport android.provider.ContactsContract.RawContactsEntity; 46173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.util.Log; 472b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.widget.Toast; 48173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 49e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.contacts.model.AccountTypeManager; 50851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.RawContactModifier; 51851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.RawContactDelta; 52851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.RawContactDeltaList; 53851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.account.AccountWithDataSet; 54e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.contacts.util.CallerInfoCacheUtils; 55e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Lists; 56e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Sets; 57e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Cheng 58e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargusimport java.io.File; 59e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargusimport java.io.FileInputStream; 60e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargusimport java.io.FileOutputStream; 61e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargusimport java.io.IOException; 62c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.ArrayList; 63c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.HashSet; 64c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.List; 65c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.concurrent.CopyOnWriteArrayList; 66173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 6718ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov/** 6818ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov * A service responsible for saving changes to the content provider. 6918ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov */ 70173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannpublic class ContactSaveService extends IntentService { 71173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann private static final String TAG = "ContactSaveService"; 72173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 73a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan /** Set to true in order to view logs on content provider operations */ 74a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan private static final boolean DEBUG = false; 75a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan 76caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String ACTION_NEW_RAW_CONTACT = "newRawContact"; 77caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 78caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_ACCOUNT_NAME = "accountName"; 79caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_ACCOUNT_TYPE = "accountType"; 802b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro public static final String EXTRA_DATA_SET = "dataSet"; 81caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_CONTENT_VALUES = "contentValues"; 82caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_CALLBACK_INTENT = "callbackIntent"; 83caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 84a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String ACTION_SAVE_CONTACT = "saveContact"; 85a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String EXTRA_CONTACT_STATE = "state"; 86a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String EXTRA_SAVE_MODE = "saveMode"; 87ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson public static final String EXTRA_SAVE_IS_PROFILE = "saveIsProfile"; 8836d24d7ede42a252c82c4aa783b2231c5e2eea79Dave Santoro public static final String EXTRA_SAVE_SUCCEEDED = "saveSucceeded"; 89e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus public static final String EXTRA_UPDATED_PHOTOS = "updatedPhotos"; 90173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 911ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov public static final String ACTION_CREATE_GROUP = "createGroup"; 92e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String ACTION_RENAME_GROUP = "renameGroup"; 93e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String ACTION_DELETE_GROUP = "deleteGroup"; 942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String ACTION_UPDATE_GROUP = "updateGroup"; 95e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String EXTRA_GROUP_ID = "groupId"; 96e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String EXTRA_GROUP_LABEL = "groupLabel"; 972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String EXTRA_RAW_CONTACTS_TO_ADD = "rawContactsToAdd"; 982d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String EXTRA_RAW_CONTACTS_TO_REMOVE = "rawContactsToRemove"; 99e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 1009d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String ACTION_SET_STARRED = "setStarred"; 1017d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov public static final String ACTION_DELETE_CONTACT = "delete"; 1029d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String EXTRA_CONTACT_URI = "contactUri"; 1039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String EXTRA_STARRED_FLAG = "starred"; 1049d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 1050f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary"; 1060f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String ACTION_CLEAR_PRIMARY = "clearPrimary"; 1070f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String EXTRA_DATA_ID = "dataId"; 1080f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 1092b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String ACTION_JOIN_CONTACTS = "joinContacts"; 1102b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String EXTRA_CONTACT_ID1 = "contactId1"; 1112b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String EXTRA_CONTACT_ID2 = "contactId2"; 1122b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String EXTRA_CONTACT_WRITABLE = "contactWritable"; 1132b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 114683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String ACTION_SET_SEND_TO_VOICEMAIL = "sendToVoicemail"; 115683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String EXTRA_SEND_TO_VOICEMAIL_FLAG = "sendToVoicemailFlag"; 116683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 117683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String ACTION_SET_RINGTONE = "setRingtone"; 118683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String EXTRA_CUSTOM_RINGTONE = "customRingtone"; 119683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 120caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet( 121caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.MIMETYPE, 122caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.IS_PRIMARY, 123caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA1, 124caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA2, 125caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA3, 126caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA4, 127caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA5, 128caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA6, 129caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA7, 130caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA8, 131caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA9, 132caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA10, 133caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA11, 134caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA12, 135caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA13, 136caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA14, 137caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA15 138caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ); 139caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 140a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov private static final int PERSIST_TRIES = 3; 141a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 1423a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public interface Listener { 1433a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public void onServiceCompleted(Intent callbackIntent); 1443a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1453a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 146a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson private static final CopyOnWriteArrayList<Listener> sListeners = 147a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson new CopyOnWriteArrayList<Listener>(); 1483a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1493a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov private Handler mMainHandler; 1503a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 151173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann public ContactSaveService() { 152173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann super(TAG); 153173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann setIntentRedelivery(true); 1543a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler = new Handler(Looper.getMainLooper()); 1553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1563a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1573a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public static void registerListener(Listener listener) { 1583a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov if (!(listener instanceof Activity)) { 1593a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov throw new ClassCastException("Only activities can be registered to" 1603a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov + " receive callback from " + ContactSaveService.class.getName()); 1613a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 162a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson sListeners.add(0, listener); 1633a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1643a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1653a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public static void unregisterListener(Listener listener) { 166a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson sListeners.remove(listener); 167173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann } 168173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 169173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann @Override 170a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public Object getSystemService(String name) { 171a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Object service = super.getSystemService(name); 172a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (service != null) { 173a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return service; 174a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 175a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 176a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return getApplicationContext().getSystemService(name); 177a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 178a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 179a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov @Override 180173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann protected void onHandleIntent(Intent intent) { 1812f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa // Call an appropriate method. If we're sure it affects how incoming phone calls are 1822f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa // handled, then notify the fact to in-call screen. 183caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String action = intent.getAction(); 184caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov if (ACTION_NEW_RAW_CONTACT.equals(action)) { 185caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov createRawContact(intent); 1862f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this); 187a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } else if (ACTION_SAVE_CONTACT.equals(action)) { 188a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov saveContact(intent); 1892f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this); 1901ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } else if (ACTION_CREATE_GROUP.equals(action)) { 1911ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov createGroup(intent); 192e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } else if (ACTION_RENAME_GROUP.equals(action)) { 193e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov renameGroup(intent); 194e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } else if (ACTION_DELETE_GROUP.equals(action)) { 195e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov deleteGroup(intent); 1962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } else if (ACTION_UPDATE_GROUP.equals(action)) { 1972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan updateGroup(intent); 1989d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } else if (ACTION_SET_STARRED.equals(action)) { 1999d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov setStarred(intent); 2000f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) { 2010f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann setSuperPrimary(intent); 2020f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } else if (ACTION_CLEAR_PRIMARY.equals(action)) { 2030f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann clearPrimary(intent); 2047d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } else if (ACTION_DELETE_CONTACT.equals(action)) { 2057d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov deleteContact(intent); 2062f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this); 2072b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } else if (ACTION_JOIN_CONTACTS.equals(action)) { 2082b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov joinContacts(intent); 2092f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this); 210683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) { 211683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson setSendToVoicemail(intent); 2122f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this); 213683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } else if (ACTION_SET_RINGTONE.equals(action)) { 214683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson setRingtone(intent); 2152f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this); 216caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 217caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 218caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 2199d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov /** 2209d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to create a new raw contact 2219d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * using data presented as a set of ContentValues. 2229d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov */ 2239d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createNewRawContactIntent(Context context, 2242b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro ArrayList<ContentValues> values, AccountWithDataSet account, 225e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 2269d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent( 2279d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov context, ContactSaveService.class); 2289d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT); 2299d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov if (account != null) { 2309d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name); 2319d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type); 2322b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet); 2339d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 2349d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putParcelableArrayListExtra( 2359d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov ContactSaveService.EXTRA_CONTENT_VALUES, values); 2369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 2379d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // Callback intent will be invoked by the service once the new contact is 2389d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // created. The service will put the URI of the new contact as "data" on 2399d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // the callback intent. 2409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 2419d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov callbackIntent.setAction(callbackAction); 2429d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 2439d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov return serviceIntent; 2449d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 2459d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 246caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov private void createRawContact(Intent intent) { 247caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME); 248caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE); 2492b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String dataSet = intent.getStringExtra(EXTRA_DATA_SET); 250caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES); 251caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 252caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 253caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); 254caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) 255caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValue(RawContacts.ACCOUNT_NAME, accountName) 256caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValue(RawContacts.ACCOUNT_TYPE, accountType) 2572b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro .withValue(RawContacts.DATA_SET, dataSet) 258caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .build()); 259caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 260caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov int size = valueList.size(); 261caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov for (int i = 0; i < size; i++) { 262caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentValues values = valueList.get(i); 263caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov values.keySet().retainAll(ALLOWED_DATA_COLUMNS); 264caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) 265caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValueBackReference(Data.RAW_CONTACT_ID, 0) 266caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValues(values) 267caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .build()); 268caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 269caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 270caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentResolver resolver = getContentResolver(); 271caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentProviderResult[] results; 272caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov try { 273caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov results = resolver.applyBatch(ContactsContract.AUTHORITY, operations); 274caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } catch (Exception e) { 275caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov throw new RuntimeException("Failed to store new contact", e); 276caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 277caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 278caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Uri rawContactUri = results[0].uri; 279caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri)); 280caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 2813a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 282caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 283caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 284caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov /** 285a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov * Creates an intent that can be sent to this service to create a new raw contact 286a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov * using data presented as a set of ContentValues. 287e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * This variant is more convenient to use when there is only one photo that can 288e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * possibly be updated, as in the Contact Details screen. 289e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @param rawContactId identifies a writable raw-contact whose photo is to be updated. 290e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @param updatedPhotoPath denotes a temporary file containing the contact's new photo. 291a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov */ 292851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu public static Intent createSaveContactIntent(Context context, RawContactDeltaList state, 293e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus String saveModeExtraKey, int saveMode, boolean isProfile, 294e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction, long rawContactId, 295e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus String updatedPhotoPath) { 296e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus Bundle bundle = new Bundle(); 297e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus bundle.putString(String.valueOf(rawContactId), updatedPhotoPath); 298e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus return createSaveContactIntent(context, state, saveModeExtraKey, saveMode, isProfile, 299e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus callbackActivity, callbackAction, bundle); 300e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 301e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 302e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus /** 303e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * Creates an intent that can be sent to this service to create a new raw contact 304e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * using data presented as a set of ContentValues. 305e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * This variant is used when multiple contacts' photos may be updated, as in the 306e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * Contact Editor. 307e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @param updatedPhotos maps each raw-contact's ID to the file-path of the new photo. 308e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus */ 309851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu public static Intent createSaveContactIntent(Context context, RawContactDeltaList state, 310e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus String saveModeExtraKey, int saveMode, boolean isProfile, 311e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction, 312e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Bundle updatedPhotos) { 313a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Intent serviceIntent = new Intent( 314a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov context, ContactSaveService.class); 315a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT); 316a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state); 317ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson serviceIntent.putExtra(EXTRA_SAVE_IS_PROFILE, isProfile); 318e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus if (updatedPhotos != null) { 319e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus serviceIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos); 320e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 321a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 322e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus if (callbackActivity != null) { 323e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // Callback intent will be invoked by the service once the contact is 324e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // saved. The service will put the URI of the new contact as "data" on 325e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // the callback intent. 326e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Intent callbackIntent = new Intent(context, callbackActivity); 327e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.putExtra(saveModeExtraKey, saveMode); 328e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.setAction(callbackAction); 329e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 330e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus } 331a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return serviceIntent; 332a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 333a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 334a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov private void saveContact(Intent intent) { 335851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu RawContactDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE); 336ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false); 337e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus Bundle updatedPhotos = intent.getParcelableExtra(EXTRA_UPDATED_PHOTOS); 338a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 339a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Trim any empty fields, and RawContacts, before persisting 340a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this); 341851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu RawContactModifier.trimEmpty(state, accountTypes); 342a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 343a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Uri lookupUri = null; 344a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 345a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ContentResolver resolver = getContentResolver(); 346e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus boolean succeeded = false; 347a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 348ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // Keep track of the id of a newly raw-contact (if any... there can be at most one). 349ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus long insertedRawContactId = -1; 350ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 351a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Attempt to persist changes 352a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov int tries = 0; 353a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov while (tries++ < PERSIST_TRIES) { 354a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov try { 355a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Build operations and try applying 356a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ArrayList<ContentProviderOperation> diff = state.buildDiff(); 357a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan if (DEBUG) { 358a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan Log.v(TAG, "Content Provider Operations:"); 359a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan for (ContentProviderOperation operation : diff) { 360a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan Log.v(TAG, operation.toString()); 361a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan } 362a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan } 363a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan 364a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov ContentProviderResult[] results = null; 365a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (!diff.isEmpty()) { 366a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov results = resolver.applyBatch(ContactsContract.AUTHORITY, diff); 367a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 368a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 369a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final long rawContactId = getRawContactId(state, diff, results); 370a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (rawContactId == -1) { 371a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov throw new IllegalStateException("Could not determine RawContact ID after save"); 372a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 373ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // We don't have to check to see if the value is still -1. If we reach here, 374ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // the previous loop iteration didn't succeed, so any ID that we obtained is bogus. 375ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus insertedRawContactId = getInsertedRawContactId(diff, results); 3767c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro if (isProfile) { 3777c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro // Since the profile supports local raw contacts, which may have been completely 3787c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro // removed if all information was removed, we need to do a special query to 3797c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro // get the lookup URI for the profile contact (if it still exists). 3807c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro Cursor c = resolver.query(Profile.CONTENT_URI, 3817c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro new String[] {Contacts._ID, Contacts.LOOKUP_KEY}, 3827c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro null, null, null); 3837c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro try { 384162b7e34fb581d0fa279957af5136d190c40759fErik if (c.moveToFirst()) { 385162b7e34fb581d0fa279957af5136d190c40759fErik final long contactId = c.getLong(0); 386162b7e34fb581d0fa279957af5136d190c40759fErik final String lookupKey = c.getString(1); 387162b7e34fb581d0fa279957af5136d190c40759fErik lookupUri = Contacts.getLookupUri(contactId, lookupKey); 388162b7e34fb581d0fa279957af5136d190c40759fErik } 3897c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } finally { 3907c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro c.close(); 3917c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } 3927c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } else { 3937c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, 3947c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro rawContactId); 3957c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri); 3967c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } 397a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Log.v(TAG, "Saved contact. New URI: " + lookupUri); 398e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 399e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus // We can change this back to false later, if we fail to save the contact photo. 400e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus succeeded = true; 401a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov break; 402a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 403a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } catch (RemoteException e) { 404a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Something went wrong, bail without success 405a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Log.e(TAG, "Problem persisting user edits", e); 406a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov break; 407a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 408a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } catch (OperationApplicationException e) { 409a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Version consistency failed, re-parent change and try again 410a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString()); 411a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN("); 412a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov boolean first = true; 413a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final int count = state.size(); 414a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov for (int i = 0; i < count; i++) { 415a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Long rawContactId = state.getRawContactId(i); 416a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (rawContactId != null && rawContactId != -1) { 417a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (!first) { 418a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(','); 419a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 420a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(rawContactId); 421a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov first = false; 422a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 423a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 424a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(")"); 425a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 426a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (first) { 427a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov throw new IllegalStateException("Version consistency failed for a new contact"); 428a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 429a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 430851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu final RawContactDeltaList newState = RawContactDeltaList.fromQuery( 431c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro isProfile 432c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro ? RawContactsEntity.PROFILE_CONTENT_URI 433c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro : RawContactsEntity.CONTENT_URI, 434c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro resolver, sb.toString(), null, null); 435851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu state = RawContactDeltaList.mergeAfter(newState, state); 436c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro 437c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro // Update the new state to use profile URIs if appropriate. 438c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro if (isProfile) { 439851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu for (RawContactDelta delta : state) { 440c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro delta.setProfileQueryUri(); 441c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro } 442c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro } 443a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 444a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 445a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 446e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus // Now save any updated photos. We do this at the end to ensure that 447e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus // the ContactProvider already knows about newly-created contacts. 448e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus if (updatedPhotos != null) { 449e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus for (String key : updatedPhotos.keySet()) { 450e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus String photoFilePath = updatedPhotos.getString(key); 451e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus long rawContactId = Long.parseLong(key); 452ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 453ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // If the raw-contact ID is negative, we are saving a new raw-contact; 454ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // replace the bogus ID with the new one that we actually saved the contact at. 455ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus if (rawContactId < 0) { 456ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus rawContactId = insertedRawContactId; 457ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus if (rawContactId == -1) { 458ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus throw new IllegalStateException( 459ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus "Could not determine RawContact ID for image insertion"); 460ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } 461ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } 462ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 463e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus File photoFile = new File(photoFilePath); 464e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus if (!saveUpdatedPhoto(rawContactId, photoFile)) succeeded = false; 465e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 466e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 467e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 468e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 469e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus if (callbackIntent != null) { 470e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus if (succeeded) { 471e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // Mark the intent to indicate that the save was successful (even if the lookup URI 472e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // is now null). For local contacts or the local profile, it's possible that the 473e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // save triggered removal of the contact, so no lookup URI would exist.. 474e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.putExtra(EXTRA_SAVE_SUCCEEDED, true); 475e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus } 476e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.setData(lookupUri); 477e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus deliverCallback(callbackIntent); 478e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 479a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 480a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 481e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus /** 482e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * Save updated photo for the specified raw-contact. 483e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @return true for success, false for failure 484e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus */ 485e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus private boolean saveUpdatedPhoto(long rawContactId, File photoFile) { 486ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final Uri outputUri = Uri.withAppendedPath( 487e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), 488e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus RawContacts.DisplayPhoto.CONTENT_DIRECTORY); 489e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 490e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus try { 491ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final FileOutputStream outputStream = getContentResolver() 492ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus .openAssetFileDescriptor(outputUri, "rw").createOutputStream(); 493e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus try { 494ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final FileInputStream inputStream = new FileInputStream(photoFile); 495ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus try { 496ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final byte[] buffer = new byte[16 * 1024]; 497ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus int length; 498ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus int totalLength = 0; 499ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus while ((length = inputStream.read(buffer)) > 0) { 500ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus outputStream.write(buffer, 0, length); 501ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus totalLength += length; 502ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } 503ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus Log.v(TAG, "Wrote " + totalLength + " bytes for photo " + photoFile.toString()); 504ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } finally { 505ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus inputStream.close(); 506ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } 507ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } finally { 508e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus outputStream.close(); 509ebc17929007ecf12f25b8e90023766e8a083d11eJosh Gargus photoFile.delete(); 510e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 511ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } catch (IOException e) { 512ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus Log.e(TAG, "Failed to write photo: " + photoFile.toString() + " because: " + e); 513ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus return false; 514e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 515ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus return true; 516e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 517e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 518ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus /** 519ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus * Find the ID of an existing or newly-inserted raw-contact. If none exists, return -1. 520ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus */ 521851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu private long getRawContactId(RawContactDeltaList state, 522a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ArrayList<ContentProviderOperation> diff, 523a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ContentProviderResult[] results) { 524ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus long existingRawContactId = state.findRawContactId(); 525ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus if (existingRawContactId != -1) { 526ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus return existingRawContactId; 527a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 528a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 529ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus return getInsertedRawContactId(diff, results); 530ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } 531ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 532ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus /** 533ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus * Find the ID of a newly-inserted raw-contact. If none exists, return -1. 534ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus */ 535ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus private long getInsertedRawContactId( 536ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final ArrayList<ContentProviderOperation> diff, 537ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final ContentProviderResult[] results) { 538a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final int diffSize = diff.size(); 539a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov for (int i = 0; i < diffSize; i++) { 540a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov ContentProviderOperation operation = diff.get(i); 541a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (operation.getType() == ContentProviderOperation.TYPE_INSERT 542a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov && operation.getUri().getEncodedPath().contains( 543a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov RawContacts.CONTENT_URI.getEncodedPath())) { 544a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return ContentUris.parseId(results[i].uri); 545a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 546a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 547a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return -1; 548a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 549a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 550a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov /** 551717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * Creates an intent that can be sent to this service to create a new group as 552717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * well as add new members at the same time. 553717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * 554717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param context of the application 555717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param account in which the group should be created 556717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param label is the name of the group (cannot be null) 557717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param rawContactsToAdd is an array of raw contact IDs for contacts that 558717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * should be added to the group 559717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param callbackActivity is the activity to send the callback intent to 560717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param callbackAction is the intent action for the callback intent 561caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov */ 5622b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro public static Intent createNewGroupIntent(Context context, AccountWithDataSet account, 563e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus String label, long[] rawContactsToAdd, Class<? extends Activity> callbackActivity, 564717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan String callbackAction) { 5659d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 5669d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP); 5679d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type); 5689d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name); 5692b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet); 5709d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label); 571717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd); 572caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 5739d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // Callback intent will be invoked by the service once the new group is 574717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // created. 5759d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 5769d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov callbackIntent.setAction(callbackAction); 5771ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 5789d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 5791ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov return serviceIntent; 5801ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 5811ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 5821ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov private void createGroup(Intent intent) { 5832b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE); 5842b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME); 5852b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String dataSet = intent.getStringExtra(EXTRA_DATA_SET); 5862b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 587717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD); 5881ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 5891ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov ContentValues values = new ContentValues(); 5901ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.ACCOUNT_TYPE, accountType); 5911ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.ACCOUNT_NAME, accountName); 5922b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro values.put(Groups.DATA_SET, dataSet); 5931ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.TITLE, label); 5941ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 595717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final ContentResolver resolver = getContentResolver(); 596717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 597717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Create the new group 598717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values); 599717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 600717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // If there's no URI, then the insertion failed. Abort early because group members can't be 601717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // added if the group doesn't exist 6021ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov if (groupUri == null) { 603717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan Log.e(TAG, "Couldn't create group with label " + label); 6041ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov return; 6051ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 6061ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 607717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Add new group members 608717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri)); 609717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 610717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // TODO: Move this into the contact editor where it belongs. This needs to be integrated 611717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // with the way other intent extras that are passed to the {@link ContactEditorActivity}. 6121ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.clear(); 6131ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 6141ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri)); 6151ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 6161ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 617c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setData(groupUri); 618717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // TODO: This can be taken out when the above TODO is addressed 6191ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values)); 6203a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 6211ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 6221ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 6231ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov /** 6249d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to rename a group. 6251ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov */ 626c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel, 627e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 6289d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 6299d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP); 6309d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 6319d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel); 632c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 633c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan // Callback intent will be invoked by the service once the group is renamed. 634c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan Intent callbackIntent = new Intent(context, callbackActivity); 635c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setAction(callbackAction); 636c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 637c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 638caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov return serviceIntent; 639caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 640e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 641e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov private void renameGroup(Intent intent) { 642e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 643e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 644e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 645e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov if (groupId == -1) { 646e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov Log.e(TAG, "Invalid arguments for renameGroup request"); 647e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return; 648e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 649e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 650e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov ContentValues values = new ContentValues(); 651e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov values.put(Groups.TITLE, label); 652c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId); 653c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan getContentResolver().update(groupUri, values, null, null); 654c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 655c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 656c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setData(groupUri); 657c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan deliverCallback(callbackIntent); 658e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 659e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 660e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov /** 6619d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to delete a group. 662e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov */ 6639d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createGroupDeletionIntent(Context context, long groupId) { 6649d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 6659d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP); 666e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 667e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return serviceIntent; 668e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 669e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 670e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov private void deleteGroup(Intent intent) { 671e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 672e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov if (groupId == -1) { 673e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov Log.e(TAG, "Invalid arguments for deleteGroup request"); 674e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return; 675e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 676e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 677e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov getContentResolver().delete( 678e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null); 679e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 680e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 681e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov /** 6822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * Creates an intent that can be sent to this service to rename a group as 6832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * well as add and remove members from the group. 6842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * 6852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param context of the application 6862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param groupId of the group that should be modified 6872d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param newLabel is the updated name of the group (can be null if the name 6882d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * should not be updated) 6892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param rawContactsToAdd is an array of raw contact IDs for contacts that 6902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * should be added to the group 6912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param rawContactsToRemove is an array of raw contact IDs for contacts 6922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * that should be removed from the group 6932d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param callbackActivity is the activity to send the callback intent to 6942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param callbackAction is the intent action for the callback intent 6952d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan */ 6962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel, 6972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToAdd, long[] rawContactsToRemove, 698e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 6992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Intent serviceIntent = new Intent(context, ContactSaveService.class); 7002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP); 7012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 7022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel); 7032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd); 7042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_REMOVE, 7052d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactsToRemove); 7062d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7072d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Callback intent will be invoked by the service once the group is updated 7082d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Intent callbackIntent = new Intent(context, callbackActivity); 7092d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan callbackIntent.setAction(callbackAction); 7102d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 7112d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7122d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan return serviceIntent; 7132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7152d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan private void updateGroup(Intent intent) { 7162d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 7172d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 7182d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD); 7192d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToRemove = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_REMOVE); 7202d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7212d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (groupId == -1) { 7222d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.e(TAG, "Invalid arguments for updateGroup request"); 7232d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan return; 7242d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7252d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7262d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentResolver resolver = getContentResolver(); 7272d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId); 7282d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7292d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Update group name if necessary 7302d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (label != null) { 7312d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan ContentValues values = new ContentValues(); 7322d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan values.put(Groups.TITLE, label); 733717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan resolver.update(groupUri, values, null, null); 7342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 736717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Add and remove members if necessary 737717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan addMembersToGroup(resolver, rawContactsToAdd, groupId); 738717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan removeMembersFromGroup(resolver, rawContactsToRemove, groupId); 739717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 740717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 741717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan callbackIntent.setData(groupUri); 742717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan deliverCallback(callbackIntent); 743717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 744717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 74518958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann private static void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd, 746717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan long groupId) { 747717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan if (rawContactsToAdd == null) { 748717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan return; 749717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 7502d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (long rawContactId : rawContactsToAdd) { 7512d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan try { 7522d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ArrayList<ContentProviderOperation> rawContactOperations = 7532d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new ArrayList<ContentProviderOperation>(); 7542d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7552d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Build an assert operation to ensure the contact is not already in the group 7562d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentProviderOperation.Builder assertBuilder = ContentProviderOperation 7572d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan .newAssertQuery(Data.CONTENT_URI); 7582d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan assertBuilder.withSelection(Data.RAW_CONTACT_ID + "=? AND " + 7592d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?", 7602d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new String[] { String.valueOf(rawContactId), 7612d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)}); 7622d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan assertBuilder.withExpectedCount(0); 7632d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactOperations.add(assertBuilder.build()); 7642d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7652d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Build an insert operation to add the contact to the group 7662d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentProviderOperation.Builder insertBuilder = ContentProviderOperation 7672d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan .newInsert(Data.CONTENT_URI); 7682d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(Data.RAW_CONTACT_ID, rawContactId); 7692d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 7702d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(GroupMembership.GROUP_ROW_ID, groupId); 7712d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactOperations.add(insertBuilder.build()); 7722d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7732d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (DEBUG) { 7742d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (ContentProviderOperation operation : rawContactOperations) { 7752d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.v(TAG, operation.toString()); 7762d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7772d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7782d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7792d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Apply batch 7802d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (!rawContactOperations.isEmpty()) { 78118958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations); 7822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } catch (RemoteException e) { 7842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Something went wrong, bail without success 7852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.e(TAG, "Problem persisting user edits for raw contact ID " + 7862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(rawContactId), e); 7872d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } catch (OperationApplicationException e) { 7882d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // The assert could have failed because the contact is already in the group, 7892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // just continue to the next contact 7902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.w(TAG, "Assert failed in adding raw contact ID " + 7912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(rawContactId) + ". Already exists in group " + 7922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(groupId), e); 7932d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 795717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 7962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 79718958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann private static void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove, 798717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan long groupId) { 799717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan if (rawContactsToRemove == null) { 800717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan return; 801717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 8022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (long rawContactId : rawContactsToRemove) { 8032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Apply the delete operation on the data row for the given raw contact's 8042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // membership in the given group. If no contact matches the provided selection, then 8052d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // nothing will be done. Just continue to the next contact. 80618958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann resolver.delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " + 8072d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?", 8082d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new String[] { String.valueOf(rawContactId), 8092d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)}); 8102d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 8112d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 8122d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 8132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan /** 8149d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to star or un-star a contact. 815e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov */ 8169d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) { 8179d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 8189d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED); 8199d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 8209d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value); 8219d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 822e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return serviceIntent; 823e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 8249d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 8259d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov private void setStarred(Intent intent) { 8269d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 8279d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false); 8289d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov if (contactUri == null) { 8299d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Log.e(TAG, "Invalid arguments for setStarred request"); 8309d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov return; 8319d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 8329d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 8339d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov final ContentValues values = new ContentValues(1); 8349d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov values.put(Contacts.STARRED, value); 8359d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov getContentResolver().update(contactUri, values, null, null); 8369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 8370f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 8380f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann /** 839683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson * Creates an intent that can be sent to this service to set the redirect to voicemail. 840683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson */ 841683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static Intent createSetSendToVoicemail(Context context, Uri contactUri, 842683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson boolean value) { 843683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Intent serviceIntent = new Intent(context, ContactSaveService.class); 844683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.setAction(ContactSaveService.ACTION_SET_SEND_TO_VOICEMAIL); 845683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 846683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_SEND_TO_VOICEMAIL_FLAG, value); 847683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 848683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return serviceIntent; 849683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 850683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 851683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson private void setSendToVoicemail(Intent intent) { 852683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 853683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson boolean value = intent.getBooleanExtra(EXTRA_SEND_TO_VOICEMAIL_FLAG, false); 854683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson if (contactUri == null) { 855683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Log.e(TAG, "Invalid arguments for setRedirectToVoicemail"); 856683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return; 857683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 858683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 859683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson final ContentValues values = new ContentValues(1); 860683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson values.put(Contacts.SEND_TO_VOICEMAIL, value); 861683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson getContentResolver().update(contactUri, values, null, null); 862683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 863683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 864683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson /** 865683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson * Creates an intent that can be sent to this service to save the contact's ringtone. 866683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson */ 867683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static Intent createSetRingtone(Context context, Uri contactUri, 868683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson String value) { 869683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Intent serviceIntent = new Intent(context, ContactSaveService.class); 870683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.setAction(ContactSaveService.ACTION_SET_RINGTONE); 871683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 872683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CUSTOM_RINGTONE, value); 873683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 874683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return serviceIntent; 875683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 876683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 877683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson private void setRingtone(Intent intent) { 878683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 879683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson String value = intent.getStringExtra(EXTRA_CUSTOM_RINGTONE); 880683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson if (contactUri == null) { 881683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Log.e(TAG, "Invalid arguments for setRingtone"); 882683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return; 883683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 884683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson ContentValues values = new ContentValues(1); 885683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson values.put(Contacts.CUSTOM_RINGTONE, value); 886683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson getContentResolver().update(contactUri, values, null, null); 887683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 888683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 889683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson /** 8900f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * Creates an intent that sets the selected data item as super primary (default) 8910f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann */ 8920f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static Intent createSetSuperPrimaryIntent(Context context, long dataId) { 8930f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Intent serviceIntent = new Intent(context, ContactSaveService.class); 8940f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY); 8950f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId); 8960f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return serviceIntent; 8970f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 8980f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 8990f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann private void setSuperPrimary(Intent intent) { 9000f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1); 9010f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann if (dataId == -1) { 9020f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Log.e(TAG, "Invalid arguments for setSuperPrimary request"); 9030f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return; 9040f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9050f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9060f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann // Update the primary values in the data record. 9070f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann ContentValues values = new ContentValues(1); 9080f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_SUPER_PRIMARY, 1); 9090f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_PRIMARY, 1); 9100f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9110f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), 9120f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values, null, null); 9130f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9140f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9150f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann /** 9160f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * Creates an intent that clears the primary flag of all data items that belong to the same 9170f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * raw_contact as the given data item. Will only clear, if the data item was primary before 9180f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * this call 9190f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann */ 9200f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static Intent createClearPrimaryIntent(Context context, long dataId) { 9210f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Intent serviceIntent = new Intent(context, ContactSaveService.class); 9220f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY); 9230f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId); 9240f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return serviceIntent; 9250f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9260f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9270f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann private void clearPrimary(Intent intent) { 9280f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1); 9290f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann if (dataId == -1) { 9300f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Log.e(TAG, "Invalid arguments for clearPrimary request"); 9310f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return; 9320f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9330f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9340f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann // Update the primary values in the data record. 9350f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann ContentValues values = new ContentValues(1); 9360f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_SUPER_PRIMARY, 0); 9370f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_PRIMARY, 0); 9380f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9390f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), 9400f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values, null, null); 9410f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9427d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 9437d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov /** 9447d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov * Creates an intent that can be sent to this service to delete a contact. 9457d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov */ 9467d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov public static Intent createDeleteContactIntent(Context context, Uri contactUri) { 9477d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 9487d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT); 9497d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 9507d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov return serviceIntent; 9517d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 9527d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 9537d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov private void deleteContact(Intent intent) { 9547d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 9557d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov if (contactUri == null) { 9567d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Log.e(TAG, "Invalid arguments for deleteContact request"); 9577d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov return; 9587d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 9597d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 9607d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov getContentResolver().delete(contactUri, null, null); 9617d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 9622b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9632b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov /** 9642b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov * Creates an intent that can be sent to this service to join two contacts. 9652b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov */ 9662b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static Intent createJoinContactsIntent(Context context, long contactId1, 9672b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long contactId2, boolean contactWritable, 968e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 9692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 9702b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS); 9712b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1); 9722b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2); 9732b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_WRITABLE, contactWritable); 9742b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9752b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Callback intent will be invoked by the service once the contacts are joined. 9762b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 9772b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov callbackIntent.setAction(callbackAction); 9782b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 9792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9802b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov return serviceIntent; 9812b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 9822b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9832b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9842b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov private interface JoinContactQuery { 9852b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov String[] PROJECTION = { 9862b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts._ID, 9872b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts.CONTACT_ID, 9882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts.NAME_VERIFIED, 9892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts.DISPLAY_NAME_SOURCE, 9902b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov }; 9912b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?"; 9932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int _ID = 0; 9952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int CONTACT_ID = 1; 9962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int NAME_VERIFIED = 2; 9972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int DISPLAY_NAME_SOURCE = 3; 9982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 9992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov private void joinContacts(Intent intent) { 10012b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1); 10022b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1); 10032b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov boolean writable = intent.getBooleanExtra(EXTRA_CONTACT_WRITABLE, false); 10042b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (contactId1 == -1 || contactId2 == -1) { 10052b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Log.e(TAG, "Invalid arguments for joinContacts request"); 10062b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov return; 10072b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10082b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10092b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov final ContentResolver resolver = getContentResolver(); 10102b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10112b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Load raw contact IDs for all raw contacts involved - currently edited and selected 10122b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // in the join UIs 10132b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Cursor c = resolver.query(RawContacts.CONTENT_URI, 10142b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov JoinContactQuery.PROJECTION, 10152b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov JoinContactQuery.SELECTION, 10162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov new String[]{String.valueOf(contactId1), String.valueOf(contactId2)}, null); 10172b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long rawContactIds[]; 10192b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long verifiedNameRawContactId = -1; 10202b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov try { 10212b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int maxDisplayNameSource = -1; 10222b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov rawContactIds = new long[c.getCount()]; 10232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int i = 0; i < rawContactIds.length; i++) { 10242b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov c.moveToPosition(i); 10252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long rawContactId = c.getLong(JoinContactQuery._ID); 10262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov rawContactIds[i] = rawContactId; 10272b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE); 10282b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (nameSource > maxDisplayNameSource) { 10292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov maxDisplayNameSource = nameSource; 10302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10322b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Find an appropriate display name for the joined contact: 10342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // if should have a higher DisplayNameSource or be the name 10352b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // of the original contact that we are joining with another. 10362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (writable) { 10372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int i = 0; i < rawContactIds.length; i++) { 10382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov c.moveToPosition(i); 10392b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (c.getLong(JoinContactQuery.CONTACT_ID) == contactId1) { 10402b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE); 10412b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (nameSource == maxDisplayNameSource 10422b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov && (verifiedNameRawContactId == -1 10432b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0)) { 10442b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov verifiedNameRawContactId = c.getLong(JoinContactQuery._ID); 10452b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10462b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10472b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } finally { 10502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov c.close(); 10512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // For each pair of raw contacts, insert an aggregation exception 10542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); 10552b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int i = 0; i < rawContactIds.length; i++) { 10562b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int j = 0; j < rawContactIds.length; j++) { 10572b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (i != j) { 10582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]); 10592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10612b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10622b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10632b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Mark the original contact as "name verified" to make sure that the contact 10642b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // display name does not change as a result of the join 10652b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (verifiedNameRawContactId != -1) { 10662b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Builder builder = ContentProviderOperation.newUpdate( 10672b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId)); 10682b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(RawContacts.NAME_VERIFIED, 1); 10692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov operations.add(builder.build()); 10702b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10712b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10722b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov boolean success = false; 10732b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Apply all aggregation exceptions as one batch 10742b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov try { 10752b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov resolver.applyBatch(ContactsContract.AUTHORITY, operations); 1076886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov showToast(R.string.contactsJoinedMessage); 10772b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov success = true; 10782b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } catch (RemoteException e) { 10792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Log.e(TAG, "Failed to apply aggregation exception batch", e); 1080886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov showToast(R.string.contactSavedErrorToast); 10812b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } catch (OperationApplicationException e) { 10822b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Log.e(TAG, "Failed to apply aggregation exception batch", e); 1083886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov showToast(R.string.contactSavedErrorToast); 10842b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10852b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10862b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 10872b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (success) { 10882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Uri uri = RawContacts.getContactLookupUri(resolver, 10892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0])); 10902b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov callbackIntent.setData(uri); 10912b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10923a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 10932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov /** 10962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation. 10972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov */ 10982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations, 10992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long rawContactId1, long rawContactId2) { 11002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Builder builder = 11012b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI); 11022b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER); 11032b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1); 11042b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2); 11052b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov operations.add(builder.build()); 11062b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 1107886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov 1108886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov /** 1109886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov * Shows a toast on the UI thread. 1110886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov */ 1111886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov private void showToast(final int message) { 11123a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler.post(new Runnable() { 1113886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov 1114886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov @Override 1115886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov public void run() { 1116886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov Toast.makeText(ContactSaveService.this, message, Toast.LENGTH_LONG).show(); 1117886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov } 1118886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov }); 1119886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov } 11203a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 11213a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov private void deliverCallback(final Intent callbackIntent) { 11223a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler.post(new Runnable() { 11233a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 11243a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov @Override 11253a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public void run() { 11263a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallbackOnUiThread(callbackIntent); 11273a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 11283a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov }); 11293a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 11303a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 11313a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov void deliverCallbackOnUiThread(final Intent callbackIntent) { 11323a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // TODO: this assumes that if there are multiple instances of the same 11333a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // activity registered, the last one registered is the one waiting for 11343a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // the callback. Validity of this assumption needs to be verified. 1135a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson for (Listener listener : sListeners) { 1136a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson if (callbackIntent.getComponent().equals( 1137a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson ((Activity) listener).getIntent().getComponent())) { 1138a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson listener.onServiceCompleted(callbackIntent); 1139a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson return; 11403a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 11413a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 11423a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1143173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann} 1144