ContactSaveService.java revision 0653de3dbac5b4f4a41332f09a2275a96dab4c8e
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; 40548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwellimport android.provider.ContactsContract.CommonDataKinds.StructuredName; 419d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikovimport android.provider.ContactsContract.Contacts; 42caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.Data; 43e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikovimport android.provider.ContactsContract.Groups; 44e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Leeimport android.provider.ContactsContract.PinnedPositions; 45ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelsonimport android.provider.ContactsContract.Profile; 46caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.RawContacts; 47c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoroimport android.provider.ContactsContract.RawContactsEntity; 48173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.util.Log; 492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.widget.Toast; 50173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 51d7ca03e23948c3b2d5f97ec5598d8b50e3fc0b25Chiao Chengimport com.android.contacts.common.database.ContactUpdateUtils; 520d5588da244d0992c3ff8f25d0875fdf95a8c644Chiao Chengimport com.android.contacts.common.model.AccountTypeManager; 53cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactDelta; 54cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactDeltaList; 55cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactModifier; 56428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Chengimport com.android.contacts.common.model.account.AccountWithDataSet; 573e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jangimport com.android.contacts.editor.ContactEditorFragment; 58637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport com.android.contacts.util.ContactPhotoUtils; 59637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee 60e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Lists; 61e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Sets; 62e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Cheng 63c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.ArrayList; 64c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.HashSet; 65c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.List; 66c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.concurrent.CopyOnWriteArrayList; 67173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 6818ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov/** 6918ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov * A service responsible for saving changes to the content provider. 7018ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov */ 71173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannpublic class ContactSaveService extends IntentService { 72173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann private static final String TAG = "ContactSaveService"; 73173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 74a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan /** Set to true in order to view logs on content provider operations */ 75a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan private static final boolean DEBUG = false; 76a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan 77caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String ACTION_NEW_RAW_CONTACT = "newRawContact"; 78caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 79caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_ACCOUNT_NAME = "accountName"; 80caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_ACCOUNT_TYPE = "accountType"; 812b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro public static final String EXTRA_DATA_SET = "dataSet"; 82caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_CONTENT_VALUES = "contentValues"; 83caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_CALLBACK_INTENT = "callbackIntent"; 84caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 85a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String ACTION_SAVE_CONTACT = "saveContact"; 86a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String EXTRA_CONTACT_STATE = "state"; 87a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String EXTRA_SAVE_MODE = "saveMode"; 88ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson public static final String EXTRA_SAVE_IS_PROFILE = "saveIsProfile"; 8936d24d7ede42a252c82c4aa783b2231c5e2eea79Dave Santoro public static final String EXTRA_SAVE_SUCCEEDED = "saveSucceeded"; 90e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus public static final String EXTRA_UPDATED_PHOTOS = "updatedPhotos"; 91173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 921ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov public static final String ACTION_CREATE_GROUP = "createGroup"; 93e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String ACTION_RENAME_GROUP = "renameGroup"; 94e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String ACTION_DELETE_GROUP = "deleteGroup"; 952d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String ACTION_UPDATE_GROUP = "updateGroup"; 96e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String EXTRA_GROUP_ID = "groupId"; 97e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String EXTRA_GROUP_LABEL = "groupLabel"; 982d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String EXTRA_RAW_CONTACTS_TO_ADD = "rawContactsToAdd"; 992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String EXTRA_RAW_CONTACTS_TO_REMOVE = "rawContactsToRemove"; 100e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 1019d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String ACTION_SET_STARRED = "setStarred"; 1027d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov public static final String ACTION_DELETE_CONTACT = "delete"; 103d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell public static final String ACTION_DELETE_MULTIPLE_CONTACTS = "deleteMultipleContacts"; 1049d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String EXTRA_CONTACT_URI = "contactUri"; 105d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell public static final String EXTRA_CONTACT_IDS = "contactIds"; 1069d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String EXTRA_STARRED_FLAG = "starred"; 1079d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 1080f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary"; 1090f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String ACTION_CLEAR_PRIMARY = "clearPrimary"; 1100f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String EXTRA_DATA_ID = "dataId"; 1110f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 1122b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String ACTION_JOIN_CONTACTS = "joinContacts"; 113d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell public static final String ACTION_JOIN_SEVERAL_CONTACTS = "joinSeveralContacts"; 1142b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String EXTRA_CONTACT_ID1 = "contactId1"; 1152b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String EXTRA_CONTACT_ID2 = "contactId2"; 1162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 117683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String ACTION_SET_SEND_TO_VOICEMAIL = "sendToVoicemail"; 118683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String EXTRA_SEND_TO_VOICEMAIL_FLAG = "sendToVoicemailFlag"; 119683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 120683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String ACTION_SET_RINGTONE = "setRingtone"; 121683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String EXTRA_CUSTOM_RINGTONE = "customRingtone"; 122683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 123caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet( 124caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.MIMETYPE, 125caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.IS_PRIMARY, 126caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA1, 127caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA2, 128caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA3, 129caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA4, 130caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA5, 131caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA6, 132caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA7, 133caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA8, 134caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA9, 135caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA10, 136caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA11, 137caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA12, 138caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA13, 139caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA14, 140caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA15 141caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ); 142caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 143a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov private static final int PERSIST_TRIES = 3; 144a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 1450653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang private static final int MAX_CONTACTS_PROVIDER_BATCH_SIZE = 499; 1460653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang 1473a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public interface Listener { 1483a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public void onServiceCompleted(Intent callbackIntent); 1493a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1503a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 151a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson private static final CopyOnWriteArrayList<Listener> sListeners = 152a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson new CopyOnWriteArrayList<Listener>(); 1533a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1543a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov private Handler mMainHandler; 1553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 156173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann public ContactSaveService() { 157173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann super(TAG); 158173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann setIntentRedelivery(true); 1593a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler = new Handler(Looper.getMainLooper()); 1603a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1613a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1623a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public static void registerListener(Listener listener) { 1633a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov if (!(listener instanceof Activity)) { 1643a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov throw new ClassCastException("Only activities can be registered to" 1653a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov + " receive callback from " + ContactSaveService.class.getName()); 1663a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 167a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson sListeners.add(0, listener); 1683a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1693a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1703a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public static void unregisterListener(Listener listener) { 171a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson sListeners.remove(listener); 172173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann } 173173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 174173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann @Override 175a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public Object getSystemService(String name) { 176a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Object service = super.getSystemService(name); 177a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (service != null) { 178a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return service; 179a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 180a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 181a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return getApplicationContext().getSystemService(name); 182a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 183a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 184a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov @Override 185173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann protected void onHandleIntent(Intent intent) { 1863a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner if (intent == null) { 1873a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner Log.d(TAG, "onHandleIntent: could not handle null intent"); 1883a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner return; 1893a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner } 1902f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa // Call an appropriate method. If we're sure it affects how incoming phone calls are 1912f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa // handled, then notify the fact to in-call screen. 192caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String action = intent.getAction(); 193caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov if (ACTION_NEW_RAW_CONTACT.equals(action)) { 194caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov createRawContact(intent); 195a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } else if (ACTION_SAVE_CONTACT.equals(action)) { 196a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov saveContact(intent); 1971ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } else if (ACTION_CREATE_GROUP.equals(action)) { 1981ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov createGroup(intent); 199e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } else if (ACTION_RENAME_GROUP.equals(action)) { 200e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov renameGroup(intent); 201e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } else if (ACTION_DELETE_GROUP.equals(action)) { 202e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov deleteGroup(intent); 2032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } else if (ACTION_UPDATE_GROUP.equals(action)) { 2042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan updateGroup(intent); 2059d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } else if (ACTION_SET_STARRED.equals(action)) { 2069d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov setStarred(intent); 2070f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) { 2080f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann setSuperPrimary(intent); 2090f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } else if (ACTION_CLEAR_PRIMARY.equals(action)) { 2100f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann clearPrimary(intent); 211d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell } else if (ACTION_DELETE_MULTIPLE_CONTACTS.equals(action)) { 212d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell deleteMultipleContacts(intent); 2137d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } else if (ACTION_DELETE_CONTACT.equals(action)) { 2147d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov deleteContact(intent); 2152b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } else if (ACTION_JOIN_CONTACTS.equals(action)) { 2162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov joinContacts(intent); 217d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } else if (ACTION_JOIN_SEVERAL_CONTACTS.equals(action)) { 218d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell joinSeveralContacts(intent); 219683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) { 220683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson setSendToVoicemail(intent); 221683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } else if (ACTION_SET_RINGTONE.equals(action)) { 222683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson setRingtone(intent); 223caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 224caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 225caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 2269d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov /** 2279d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to create a new raw contact 2289d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * using data presented as a set of ContentValues. 2299d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov */ 2309d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createNewRawContactIntent(Context context, 2312b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro ArrayList<ContentValues> values, AccountWithDataSet account, 232e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 2339d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent( 2349d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov context, ContactSaveService.class); 2359d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT); 2369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov if (account != null) { 2379d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name); 2389d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type); 2392b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet); 2409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 2419d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putParcelableArrayListExtra( 2429d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov ContactSaveService.EXTRA_CONTENT_VALUES, values); 2439d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 2449d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // Callback intent will be invoked by the service once the new contact is 2459d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // created. The service will put the URI of the new contact as "data" on 2469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // the callback intent. 2479d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 2489d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov callbackIntent.setAction(callbackAction); 2499d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 2509d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov return serviceIntent; 2519d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 2529d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 253caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov private void createRawContact(Intent intent) { 254caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME); 255caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE); 2562b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String dataSet = intent.getStringExtra(EXTRA_DATA_SET); 257caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES); 258caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 259caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 260caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); 261caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) 262caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValue(RawContacts.ACCOUNT_NAME, accountName) 263caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValue(RawContacts.ACCOUNT_TYPE, accountType) 2642b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro .withValue(RawContacts.DATA_SET, dataSet) 265caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .build()); 266caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 267caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov int size = valueList.size(); 268caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov for (int i = 0; i < size; i++) { 269caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentValues values = valueList.get(i); 270caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov values.keySet().retainAll(ALLOWED_DATA_COLUMNS); 271caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) 272caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValueBackReference(Data.RAW_CONTACT_ID, 0) 273caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValues(values) 274caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .build()); 275caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 276caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 277caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentResolver resolver = getContentResolver(); 278caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentProviderResult[] results; 279caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov try { 280caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov results = resolver.applyBatch(ContactsContract.AUTHORITY, operations); 281caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } catch (Exception e) { 282caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov throw new RuntimeException("Failed to store new contact", e); 283caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 284caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 285caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Uri rawContactUri = results[0].uri; 286caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri)); 287caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 2883a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 289caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 290caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 291caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov /** 292a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov * Creates an intent that can be sent to this service to create a new raw contact 293a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov * using data presented as a set of ContentValues. 294e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * This variant is more convenient to use when there is only one photo that can 295e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * possibly be updated, as in the Contact Details screen. 296e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @param rawContactId identifies a writable raw-contact whose photo is to be updated. 297e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @param updatedPhotoPath denotes a temporary file containing the contact's new photo. 298a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov */ 299851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu public static Intent createSaveContactIntent(Context context, RawContactDeltaList state, 300e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus String saveModeExtraKey, int saveMode, boolean isProfile, 301e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction, long rawContactId, 302637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee Uri updatedPhotoPath) { 303e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus Bundle bundle = new Bundle(); 304637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee bundle.putParcelable(String.valueOf(rawContactId), updatedPhotoPath); 305e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus return createSaveContactIntent(context, state, saveModeExtraKey, saveMode, isProfile, 3063e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang callbackActivity, callbackAction, bundle, /* backPressed =*/ false); 307e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 308e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 309e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus /** 310e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * Creates an intent that can be sent to this service to create a new raw contact 311e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * using data presented as a set of ContentValues. 312e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * This variant is used when multiple contacts' photos may be updated, as in the 313e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * Contact Editor. 314e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @param updatedPhotos maps each raw-contact's ID to the file-path of the new photo. 3153e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang * @param backPressed whether the save was initiated as a result of a back button press 3163e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang * or because the framework stopped the editor Activity 317e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus */ 318851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu public static Intent createSaveContactIntent(Context context, RawContactDeltaList state, 319e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus String saveModeExtraKey, int saveMode, boolean isProfile, 320e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction, 3213e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang Bundle updatedPhotos, boolean backPressed) { 322a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Intent serviceIntent = new Intent( 323a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov context, ContactSaveService.class); 324a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT); 325a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state); 326ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson serviceIntent.putExtra(EXTRA_SAVE_IS_PROFILE, isProfile); 327e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus if (updatedPhotos != null) { 328e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus serviceIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos); 329e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 330a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 331e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus if (callbackActivity != null) { 332e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // Callback intent will be invoked by the service once the contact is 333e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // saved. The service will put the URI of the new contact as "data" on 334e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // the callback intent. 335e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Intent callbackIntent = new Intent(context, callbackActivity); 336e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.putExtra(saveModeExtraKey, saveMode); 337e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.setAction(callbackAction); 3381e8801bc9bc60bdd1c95f582c460590272cfad64Walter Jang if (updatedPhotos != null) { 3391e8801bc9bc60bdd1c95f582c460590272cfad64Walter Jang callbackIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos); 3401e8801bc9bc60bdd1c95f582c460590272cfad64Walter Jang } 3413e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang callbackIntent.putExtra(ContactEditorFragment.INTENT_EXTRA_SAVE_BACK_PRESSED, 3423e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang backPressed); 343e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 344e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus } 345a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return serviceIntent; 346a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 347a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 348a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov private void saveContact(Intent intent) { 349851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu RawContactDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE); 350ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false); 351e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus Bundle updatedPhotos = intent.getParcelableExtra(EXTRA_UPDATED_PHOTOS); 352a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 3530809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner if (state == null) { 3540809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner Log.e(TAG, "Invalid arguments for saveContact request"); 3550809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner return; 3560809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner } 3570809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner 358a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Trim any empty fields, and RawContacts, before persisting 359a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this); 360851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu RawContactModifier.trimEmpty(state, accountTypes); 361a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 362a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Uri lookupUri = null; 363a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 364a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ContentResolver resolver = getContentResolver(); 365e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus boolean succeeded = false; 366a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 367ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // Keep track of the id of a newly raw-contact (if any... there can be at most one). 368ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus long insertedRawContactId = -1; 369ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 370a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Attempt to persist changes 371a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov int tries = 0; 372a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov while (tries++ < PERSIST_TRIES) { 373a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov try { 374a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Build operations and try applying 375a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ArrayList<ContentProviderOperation> diff = state.buildDiff(); 376a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan if (DEBUG) { 377a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan Log.v(TAG, "Content Provider Operations:"); 378a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan for (ContentProviderOperation operation : diff) { 379a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan Log.v(TAG, operation.toString()); 380a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan } 381a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan } 382a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan 383a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov ContentProviderResult[] results = null; 384a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (!diff.isEmpty()) { 385a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov results = resolver.applyBatch(ContactsContract.AUTHORITY, diff); 386511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner if (results == null) { 387511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner Log.w(TAG, "Resolver.applyBatch failed in saveContacts"); 388511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner // Retry save 389511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner continue; 390511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner } 391a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 392a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 393a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final long rawContactId = getRawContactId(state, diff, results); 394a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (rawContactId == -1) { 395a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov throw new IllegalStateException("Could not determine RawContact ID after save"); 396a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 397ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // We don't have to check to see if the value is still -1. If we reach here, 398ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // the previous loop iteration didn't succeed, so any ID that we obtained is bogus. 399ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus insertedRawContactId = getInsertedRawContactId(diff, results); 4007c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro if (isProfile) { 4017c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro // Since the profile supports local raw contacts, which may have been completely 4027c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro // removed if all information was removed, we need to do a special query to 4037c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro // get the lookup URI for the profile contact (if it still exists). 4047c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro Cursor c = resolver.query(Profile.CONTENT_URI, 4057c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro new String[] {Contacts._ID, Contacts.LOOKUP_KEY}, 4067c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro null, null, null); 407e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner if (c == null) { 408e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner continue; 409e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner } 4107c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro try { 411162b7e34fb581d0fa279957af5136d190c40759fErik if (c.moveToFirst()) { 412162b7e34fb581d0fa279957af5136d190c40759fErik final long contactId = c.getLong(0); 413162b7e34fb581d0fa279957af5136d190c40759fErik final String lookupKey = c.getString(1); 414162b7e34fb581d0fa279957af5136d190c40759fErik lookupUri = Contacts.getLookupUri(contactId, lookupKey); 415162b7e34fb581d0fa279957af5136d190c40759fErik } 4167c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } finally { 4177c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro c.close(); 4187c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } 4197c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } else { 4207c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, 4217c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro rawContactId); 4227c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri); 4237c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } 424e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner if (lookupUri != null) { 425e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner Log.v(TAG, "Saved contact. New URI: " + lookupUri); 426e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner } 427e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 428e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus // We can change this back to false later, if we fail to save the contact photo. 429e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus succeeded = true; 430a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov break; 431a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 432a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } catch (RemoteException e) { 433a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Something went wrong, bail without success 434a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Log.e(TAG, "Problem persisting user edits", e); 435a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov break; 436a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 43757fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner } catch (IllegalArgumentException e) { 43857fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner // This is thrown by applyBatch on malformed requests 43957fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner Log.e(TAG, "Problem persisting user edits", e); 44057fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner showToast(R.string.contactSavedErrorToast); 44157fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner break; 44257fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner 443a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } catch (OperationApplicationException e) { 444a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Version consistency failed, re-parent change and try again 445a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString()); 446a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN("); 447a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov boolean first = true; 448a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final int count = state.size(); 449a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov for (int i = 0; i < count; i++) { 450a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Long rawContactId = state.getRawContactId(i); 451a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (rawContactId != null && rawContactId != -1) { 452a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (!first) { 453a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(','); 454a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 455a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(rawContactId); 456a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov first = false; 457a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 458a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 459a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(")"); 460a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 461a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (first) { 4623b6c628fc9e717a41c2954b1101c3a04ad382c55Brian Attwell throw new IllegalStateException( 4633b6c628fc9e717a41c2954b1101c3a04ad382c55Brian Attwell "Version consistency failed for a new contact", e); 464a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 465a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 466851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu final RawContactDeltaList newState = RawContactDeltaList.fromQuery( 467c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro isProfile 468c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro ? RawContactsEntity.PROFILE_CONTENT_URI 469c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro : RawContactsEntity.CONTENT_URI, 470c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro resolver, sb.toString(), null, null); 471851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu state = RawContactDeltaList.mergeAfter(newState, state); 472c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro 473c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro // Update the new state to use profile URIs if appropriate. 474c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro if (isProfile) { 475851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu for (RawContactDelta delta : state) { 476c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro delta.setProfileQueryUri(); 477c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro } 478c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro } 479a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 480a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 481a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 482e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus // Now save any updated photos. We do this at the end to ensure that 483e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus // the ContactProvider already knows about newly-created contacts. 484e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus if (updatedPhotos != null) { 485e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus for (String key : updatedPhotos.keySet()) { 486637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee Uri photoUri = updatedPhotos.getParcelable(key); 487e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus long rawContactId = Long.parseLong(key); 488ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 489ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // If the raw-contact ID is negative, we are saving a new raw-contact; 490ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // replace the bogus ID with the new one that we actually saved the contact at. 491ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus if (rawContactId < 0) { 492ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus rawContactId = insertedRawContactId; 493ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } 494ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 495511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner // If the save failed, insertedRawContactId will be -1 496511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner if (rawContactId < 0 || !saveUpdatedPhoto(rawContactId, photoUri)) { 497511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner succeeded = false; 498511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner } 499e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 500e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 501e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 502e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 503e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus if (callbackIntent != null) { 504e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus if (succeeded) { 505e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // Mark the intent to indicate that the save was successful (even if the lookup URI 506e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // is now null). For local contacts or the local profile, it's possible that the 507e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // save triggered removal of the contact, so no lookup URI would exist.. 508e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.putExtra(EXTRA_SAVE_SUCCEEDED, true); 509e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus } 510e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.setData(lookupUri); 511e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus deliverCallback(callbackIntent); 512e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 513a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 514a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 515e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus /** 516e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * Save updated photo for the specified raw-contact. 517e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @return true for success, false for failure 518e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus */ 519637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee private boolean saveUpdatedPhoto(long rawContactId, Uri photoUri) { 520ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final Uri outputUri = Uri.withAppendedPath( 521e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), 522e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus RawContacts.DisplayPhoto.CONTENT_DIRECTORY); 523e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 524637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee return ContactPhotoUtils.savePhotoFromUriToUri(this, photoUri, outputUri, true); 525e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 526e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 527ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus /** 528ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus * Find the ID of an existing or newly-inserted raw-contact. If none exists, return -1. 529ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus */ 530851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu private long getRawContactId(RawContactDeltaList state, 531a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ArrayList<ContentProviderOperation> diff, 532a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ContentProviderResult[] results) { 533ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus long existingRawContactId = state.findRawContactId(); 534ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus if (existingRawContactId != -1) { 535ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus return existingRawContactId; 536a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 537a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 538ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus return getInsertedRawContactId(diff, results); 539ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } 540ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 541ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus /** 542ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus * Find the ID of a newly-inserted raw-contact. If none exists, return -1. 543ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus */ 544ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus private long getInsertedRawContactId( 545ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final ArrayList<ContentProviderOperation> diff, 546ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final ContentProviderResult[] results) { 547568f4e72711908455ccd20fbb04c1017b10d7e1cJay Shrauner if (results == null) { 548568f4e72711908455ccd20fbb04c1017b10d7e1cJay Shrauner return -1; 549568f4e72711908455ccd20fbb04c1017b10d7e1cJay Shrauner } 550a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final int diffSize = diff.size(); 5513d7edc3ef4f2521639bae91c93a8238e5a9509c1Jay Shrauner final int numResults = results.length; 5523d7edc3ef4f2521639bae91c93a8238e5a9509c1Jay Shrauner for (int i = 0; i < diffSize && i < numResults; i++) { 553a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov ContentProviderOperation operation = diff.get(i); 55413f94e1c52f553a7137e6730c09c4d28c3f54c9fBrian Attwell if (operation.isInsert() && operation.getUri().getEncodedPath().contains( 555a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov RawContacts.CONTENT_URI.getEncodedPath())) { 556a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return ContentUris.parseId(results[i].uri); 557a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 558a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 559a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return -1; 560a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 561a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 562a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov /** 563717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * Creates an intent that can be sent to this service to create a new group as 564717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * well as add new members at the same time. 565717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * 566717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param context of the application 567717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param account in which the group should be created 568717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param label is the name of the group (cannot be null) 569717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param rawContactsToAdd is an array of raw contact IDs for contacts that 570717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * should be added to the group 571717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param callbackActivity is the activity to send the callback intent to 572717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param callbackAction is the intent action for the callback intent 573caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov */ 5742b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro public static Intent createNewGroupIntent(Context context, AccountWithDataSet account, 575e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus String label, long[] rawContactsToAdd, Class<? extends Activity> callbackActivity, 576717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan String callbackAction) { 5779d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 5789d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP); 5799d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type); 5809d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name); 5812b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet); 5829d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label); 583717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd); 584caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 5859d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // Callback intent will be invoked by the service once the new group is 586717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // created. 5879d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 5889d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov callbackIntent.setAction(callbackAction); 5891ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 5909d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 5911ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov return serviceIntent; 5921ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 5931ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 5941ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov private void createGroup(Intent intent) { 5952b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE); 5962b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME); 5972b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String dataSet = intent.getStringExtra(EXTRA_DATA_SET); 5982b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 599717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD); 6001ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 6011ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov ContentValues values = new ContentValues(); 6021ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.ACCOUNT_TYPE, accountType); 6031ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.ACCOUNT_NAME, accountName); 6042b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro values.put(Groups.DATA_SET, dataSet); 6051ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.TITLE, label); 6061ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 607717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final ContentResolver resolver = getContentResolver(); 608717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 609717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Create the new group 610717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values); 611717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 612717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // If there's no URI, then the insertion failed. Abort early because group members can't be 613717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // added if the group doesn't exist 6141ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov if (groupUri == null) { 615717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan Log.e(TAG, "Couldn't create group with label " + label); 6161ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov return; 6171ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 6181ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 619717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Add new group members 620717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri)); 621717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 622717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // TODO: Move this into the contact editor where it belongs. This needs to be integrated 623717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // with the way other intent extras that are passed to the {@link ContactEditorActivity}. 6241ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.clear(); 6251ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 6261ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri)); 6271ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 6281ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 629c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setData(groupUri); 630717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // TODO: This can be taken out when the above TODO is addressed 6311ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values)); 6323a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 6331ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 6341ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 6351ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov /** 6369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to rename a group. 6371ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov */ 638c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel, 639e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 6409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 6419d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP); 6429d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 6439d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel); 644c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 645c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan // Callback intent will be invoked by the service once the group is renamed. 646c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan Intent callbackIntent = new Intent(context, callbackActivity); 647c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setAction(callbackAction); 648c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 649c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 650caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov return serviceIntent; 651caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 652e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 653e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov private void renameGroup(Intent intent) { 654e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 655e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 656e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 657e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov if (groupId == -1) { 658e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov Log.e(TAG, "Invalid arguments for renameGroup request"); 659e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return; 660e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 661e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 662e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov ContentValues values = new ContentValues(); 663e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov values.put(Groups.TITLE, label); 664c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId); 665c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan getContentResolver().update(groupUri, values, null, null); 666c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 667c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 668c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setData(groupUri); 669c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan deliverCallback(callbackIntent); 670e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 671e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 672e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov /** 6739d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to delete a group. 674e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov */ 6759d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createGroupDeletionIntent(Context context, long groupId) { 6769d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 6779d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP); 678e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 679e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return serviceIntent; 680e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 681e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 682e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov private void deleteGroup(Intent intent) { 683e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 684e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov if (groupId == -1) { 685e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov Log.e(TAG, "Invalid arguments for deleteGroup request"); 686e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return; 687e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 688e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 689e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov getContentResolver().delete( 690e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null); 691e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 692e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 693e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov /** 6942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * Creates an intent that can be sent to this service to rename a group as 6952d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * well as add and remove members from the group. 6962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * 6972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param context of the application 6982d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param groupId of the group that should be modified 6992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param newLabel is the updated name of the group (can be null if the name 7002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * should not be updated) 7012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param rawContactsToAdd is an array of raw contact IDs for contacts that 7022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * should be added to the group 7032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param rawContactsToRemove is an array of raw contact IDs for contacts 7042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * that should be removed from the group 7052d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param callbackActivity is the activity to send the callback intent to 7062d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param callbackAction is the intent action for the callback intent 7072d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan */ 7082d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel, 7092d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToAdd, long[] rawContactsToRemove, 710e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 7112d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Intent serviceIntent = new Intent(context, ContactSaveService.class); 7122d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP); 7132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 7142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel); 7152d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd); 7162d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_REMOVE, 7172d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactsToRemove); 7182d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7192d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Callback intent will be invoked by the service once the group is updated 7202d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Intent callbackIntent = new Intent(context, callbackActivity); 7212d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan callbackIntent.setAction(callbackAction); 7222d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 7232d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7242d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan return serviceIntent; 7252d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7262d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7272d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan private void updateGroup(Intent intent) { 7282d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 7292d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 7302d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD); 7312d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToRemove = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_REMOVE); 7322d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7332d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (groupId == -1) { 7342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.e(TAG, "Invalid arguments for updateGroup request"); 7352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan return; 7362d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7372d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7382d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentResolver resolver = getContentResolver(); 7392d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId); 7402d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7412d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Update group name if necessary 7422d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (label != null) { 7432d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan ContentValues values = new ContentValues(); 7442d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan values.put(Groups.TITLE, label); 745717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan resolver.update(groupUri, values, null, null); 7462d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7472d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 748717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Add and remove members if necessary 749717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan addMembersToGroup(resolver, rawContactsToAdd, groupId); 750717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan removeMembersFromGroup(resolver, rawContactsToRemove, groupId); 751717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 752717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 753717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan callbackIntent.setData(groupUri); 754717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan deliverCallback(callbackIntent); 755717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 756717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 75718958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann private static void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd, 758717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan long groupId) { 759717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan if (rawContactsToAdd == null) { 760717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan return; 761717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 7622d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (long rawContactId : rawContactsToAdd) { 7632d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan try { 7642d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ArrayList<ContentProviderOperation> rawContactOperations = 7652d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new ArrayList<ContentProviderOperation>(); 7662d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7672d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Build an assert operation to ensure the contact is not already in the group 7682d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentProviderOperation.Builder assertBuilder = ContentProviderOperation 7692d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan .newAssertQuery(Data.CONTENT_URI); 7702d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan assertBuilder.withSelection(Data.RAW_CONTACT_ID + "=? AND " + 7712d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?", 7722d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new String[] { String.valueOf(rawContactId), 7732d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)}); 7742d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan assertBuilder.withExpectedCount(0); 7752d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactOperations.add(assertBuilder.build()); 7762d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7772d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Build an insert operation to add the contact to the group 7782d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentProviderOperation.Builder insertBuilder = ContentProviderOperation 7792d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan .newInsert(Data.CONTENT_URI); 7802d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(Data.RAW_CONTACT_ID, rawContactId); 7812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 7822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(GroupMembership.GROUP_ROW_ID, groupId); 7832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactOperations.add(insertBuilder.build()); 7842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (DEBUG) { 7862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (ContentProviderOperation operation : rawContactOperations) { 7872d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.v(TAG, operation.toString()); 7882d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Apply batch 7922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (!rawContactOperations.isEmpty()) { 79318958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations); 7942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7952d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } catch (RemoteException e) { 7962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Something went wrong, bail without success 7972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.e(TAG, "Problem persisting user edits for raw contact ID " + 7982d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(rawContactId), e); 7992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } catch (OperationApplicationException e) { 8002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // The assert could have failed because the contact is already in the group, 8012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // just continue to the next contact 8022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.w(TAG, "Assert failed in adding raw contact ID " + 8032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(rawContactId) + ". Already exists in group " + 8042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(groupId), e); 8052d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 8062d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 807717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 8082d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 80918958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann private static void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove, 810717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan long groupId) { 811717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan if (rawContactsToRemove == null) { 812717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan return; 813717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 8142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (long rawContactId : rawContactsToRemove) { 8152d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Apply the delete operation on the data row for the given raw contact's 8162d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // membership in the given group. If no contact matches the provided selection, then 8172d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // nothing will be done. Just continue to the next contact. 81818958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann resolver.delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " + 8192d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?", 8202d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new String[] { String.valueOf(rawContactId), 8212d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)}); 8222d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 8232d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 8242d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 8252d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan /** 8269d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to star or un-star a contact. 827e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov */ 8289d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) { 8299d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 8309d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED); 8319d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 8329d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value); 8339d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 834e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return serviceIntent; 835e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 8369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 8379d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov private void setStarred(Intent intent) { 8389d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 8399d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false); 8409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov if (contactUri == null) { 8419d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Log.e(TAG, "Invalid arguments for setStarred request"); 8429d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov return; 8439d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 8449d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 8459d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov final ContentValues values = new ContentValues(1); 8469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov values.put(Contacts.STARRED, value); 8479d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov getContentResolver().update(contactUri, values, null, null); 848e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee 849e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee // Undemote the contact if necessary 850e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee final Cursor c = getContentResolver().query(contactUri, new String[] {Contacts._ID}, 851e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee null, null, null); 852c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner if (c == null) { 853c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner return; 854c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner } 855e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee try { 856e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee if (c.moveToFirst()) { 857e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee final long id = c.getLong(0); 858bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee 859bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee // Don't bother undemoting if this contact is the user's profile. 860bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee if (id < Profile.MIN_ID) { 8612d88efaf9efa059c70783acffb6ec3055e1b883bBrian Attwell PinnedPositions.undemote(getContentResolver(), id); 862bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee } 863e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee } 864e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee } finally { 865e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee c.close(); 866e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee } 8679d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 8680f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 8690f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann /** 870683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson * Creates an intent that can be sent to this service to set the redirect to voicemail. 871683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson */ 872683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static Intent createSetSendToVoicemail(Context context, Uri contactUri, 873683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson boolean value) { 874683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Intent serviceIntent = new Intent(context, ContactSaveService.class); 875683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.setAction(ContactSaveService.ACTION_SET_SEND_TO_VOICEMAIL); 876683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 877683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_SEND_TO_VOICEMAIL_FLAG, value); 878683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 879683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return serviceIntent; 880683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 881683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 882683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson private void setSendToVoicemail(Intent intent) { 883683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 884683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson boolean value = intent.getBooleanExtra(EXTRA_SEND_TO_VOICEMAIL_FLAG, false); 885683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson if (contactUri == null) { 886683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Log.e(TAG, "Invalid arguments for setRedirectToVoicemail"); 887683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return; 888683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 889683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 890683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson final ContentValues values = new ContentValues(1); 891683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson values.put(Contacts.SEND_TO_VOICEMAIL, value); 892683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson getContentResolver().update(contactUri, values, null, null); 893683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 894683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 895683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson /** 896683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson * Creates an intent that can be sent to this service to save the contact's ringtone. 897683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson */ 898683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static Intent createSetRingtone(Context context, Uri contactUri, 899683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson String value) { 900683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Intent serviceIntent = new Intent(context, ContactSaveService.class); 901683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.setAction(ContactSaveService.ACTION_SET_RINGTONE); 902683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 903683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CUSTOM_RINGTONE, value); 904683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 905683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return serviceIntent; 906683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 907683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 908683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson private void setRingtone(Intent intent) { 909683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 910683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson String value = intent.getStringExtra(EXTRA_CUSTOM_RINGTONE); 911683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson if (contactUri == null) { 912683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Log.e(TAG, "Invalid arguments for setRingtone"); 913683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return; 914683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 915683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson ContentValues values = new ContentValues(1); 916683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson values.put(Contacts.CUSTOM_RINGTONE, value); 917683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson getContentResolver().update(contactUri, values, null, null); 918683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 919683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 920683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson /** 9210f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * Creates an intent that sets the selected data item as super primary (default) 9220f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann */ 9230f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static Intent createSetSuperPrimaryIntent(Context context, long dataId) { 9240f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Intent serviceIntent = new Intent(context, ContactSaveService.class); 9250f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY); 9260f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId); 9270f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return serviceIntent; 9280f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9290f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9300f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann private void setSuperPrimary(Intent intent) { 9310f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1); 9320f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann if (dataId == -1) { 9330f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Log.e(TAG, "Invalid arguments for setSuperPrimary request"); 9340f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return; 9350f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9360f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 937d7ca03e23948c3b2d5f97ec5598d8b50e3fc0b25Chiao Cheng ContactUpdateUtils.setSuperPrimary(this, dataId); 9380f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9390f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9400f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann /** 9410f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * Creates an intent that clears the primary flag of all data items that belong to the same 9420f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * raw_contact as the given data item. Will only clear, if the data item was primary before 9430f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * this call 9440f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann */ 9450f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static Intent createClearPrimaryIntent(Context context, long dataId) { 9460f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Intent serviceIntent = new Intent(context, ContactSaveService.class); 9470f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY); 9480f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId); 9490f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return serviceIntent; 9500f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9510f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9520f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann private void clearPrimary(Intent intent) { 9530f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1); 9540f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann if (dataId == -1) { 9550f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Log.e(TAG, "Invalid arguments for clearPrimary request"); 9560f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return; 9570f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9580f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9590f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann // Update the primary values in the data record. 9600f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann ContentValues values = new ContentValues(1); 9610f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_SUPER_PRIMARY, 0); 9620f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_PRIMARY, 0); 9630f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9640f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), 9650f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values, null, null); 9660f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9677d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 9687d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov /** 9697d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov * Creates an intent that can be sent to this service to delete a contact. 9707d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov */ 9717d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov public static Intent createDeleteContactIntent(Context context, Uri contactUri) { 9727d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 9737d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT); 9747d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 9757d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov return serviceIntent; 9767d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 9777d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 978d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell /** 979d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell * Creates an intent that can be sent to this service to delete multiple contacts. 980d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell */ 981d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell public static Intent createDeleteMultipleContactsIntent(Context context, 982d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell long[] contactIds) { 983d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell Intent serviceIntent = new Intent(context, ContactSaveService.class); 984d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell serviceIntent.setAction(ContactSaveService.ACTION_DELETE_MULTIPLE_CONTACTS); 985d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_IDS, contactIds); 986d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell return serviceIntent; 987d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell } 988d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell 9897d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov private void deleteContact(Intent intent) { 9907d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 9917d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov if (contactUri == null) { 9927d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Log.e(TAG, "Invalid arguments for deleteContact request"); 9937d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov return; 9947d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 9957d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 9967d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov getContentResolver().delete(contactUri, null, null); 9977d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 9982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 999d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell private void deleteMultipleContacts(Intent intent) { 1000d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell final long[] contactIds = intent.getLongArrayExtra(EXTRA_CONTACT_IDS); 1001d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell if (contactIds == null) { 1002d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell Log.e(TAG, "Invalid arguments for deleteMultipleContacts request"); 1003d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell return; 1004d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell } 1005d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell for (long contactId : contactIds) { 1006d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell final Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); 1007d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell getContentResolver().delete(contactUri, null, null); 1008d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell } 1009e986c6b5954f7b9fea58cfb11c86b61d3defa271Brian Attwell showToast(R.string.contacts_deleted_toast); 1010d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell } 1011d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell 10122b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov /** 10132b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov * Creates an intent that can be sent to this service to join two contacts. 1014d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell * The resulting contact uses the name from {@param contactId1} if possible. 10152b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov */ 10162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static Intent createJoinContactsIntent(Context context, long contactId1, 1017d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell long contactId2, Class<? extends Activity> callbackActivity, String callbackAction) { 10182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 10192b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS); 10202b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1); 10212b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2); 10222b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Callback intent will be invoked by the service once the contacts are joined. 10242b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 10252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov callbackIntent.setAction(callbackAction); 10262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 10272b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10282b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov return serviceIntent; 10292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 1031d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell /** 1032d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell * Creates an intent to join all raw contacts inside {@param contactIds}'s contacts. 1033d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell * No special attention is paid to where the resulting contact's name is taken from. 1034d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell */ 1035d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell public static Intent createJoinSeveralContactsIntent(Context context, long[] contactIds) { 1036d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell Intent serviceIntent = new Intent(context, ContactSaveService.class); 1037d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell serviceIntent.setAction(ContactSaveService.ACTION_JOIN_SEVERAL_CONTACTS); 1038d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_IDS, contactIds); 1039d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell return serviceIntent; 1040d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1041d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 10422b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10432b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov private interface JoinContactQuery { 10442b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov String[] PROJECTION = { 10452b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts._ID, 10462b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts.CONTACT_ID, 10472b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts.DISPLAY_NAME_SOURCE, 10482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov }; 10492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int _ID = 0; 10512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int CONTACT_ID = 1; 1052548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell int DISPLAY_NAME_SOURCE = 2; 1053548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1054548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell 1055548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell private interface ContactEntityQuery { 1056548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell String[] PROJECTION = { 1057548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Contacts.Entity.DATA_ID, 1058548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Contacts.Entity.CONTACT_ID, 1059548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Contacts.Entity.IS_SUPER_PRIMARY, 1060548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell }; 1061548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell String SELECTION = Data.MIMETYPE + " = '" + StructuredName.CONTENT_ITEM_TYPE + "'" + 1062548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell " AND " + StructuredName.DISPLAY_NAME + "=" + Contacts.DISPLAY_NAME + 1063548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell " AND " + StructuredName.DISPLAY_NAME + " IS NOT NULL " + 1064548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell " AND " + StructuredName.DISPLAY_NAME + " != '' "; 1065548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell 1066548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell int DATA_ID = 0; 1067548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell int CONTACT_ID = 1; 1068548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell int IS_SUPER_PRIMARY = 2; 10692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10702b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 1071d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell private void joinSeveralContacts(Intent intent) { 1072d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell final long[] contactIds = intent.getLongArrayExtra(EXTRA_CONTACT_IDS); 1073548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell 1074d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell // Load raw contact IDs for all contacts involved. 1075d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell long rawContactIds[] = getRawContactIdsForAggregation(contactIds); 1076d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell if (rawContactIds == null) { 1077d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell Log.e(TAG, "Invalid arguments for joinSeveralContacts request"); 10782b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov return; 10792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10802b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 1081d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell // For each pair of raw contacts, insert an aggregation exception 10822b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov final ContentResolver resolver = getContentResolver(); 10830653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang // The maximum number of operations per batch (aka yield point) is 500. See b/22480225 10840653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang final int batchSize = MAX_CONTACTS_PROVIDER_BATCH_SIZE; 10850653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang final ArrayList<ContentProviderOperation> operations = new ArrayList<>(batchSize); 1086d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell for (int i = 0; i < rawContactIds.length; i++) { 1087d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell for (int j = 0; j < rawContactIds.length; j++) { 1088d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell if (i != j) { 1089d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]); 1090d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 10910653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang // Before we get to 500 we need to flush the operations list 10920653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang if (operations.size() > 0 && operations.size() % batchSize == 0) { 10930653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang if (!applyJoinOperations(resolver, operations)) { 10940653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang return; 10950653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang } 10960653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang operations.clear(); 10970653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang } 1098d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1099d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 11000653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang if (operations.size() > 0 && !applyJoinOperations(resolver, operations)) { 11010653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang return; 11020653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang } 11030653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang showToast(R.string.contactsJoinedMessage); 11040653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang } 1105d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 11060653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang /** Returns true if the batch was successfully applied and false otherwise. */ 11070653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang private boolean applyJoinOperations(ContentResolver resolver, 11080653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang ArrayList<ContentProviderOperation> operations) { 1109d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell try { 1110d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell resolver.applyBatch(ContactsContract.AUTHORITY, operations); 11110653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang return true; 1112d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } catch (RemoteException | OperationApplicationException e) { 1113d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell Log.e(TAG, "Failed to apply aggregation exception batch", e); 1114d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell showToast(R.string.contactSavedErrorToast); 11150653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang return false; 1116d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1117d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1118d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 1119d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 1120d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell private void joinContacts(Intent intent) { 1121d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1); 1122d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1); 11232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 11242b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Load raw contact IDs for all raw contacts involved - currently edited and selected 1125548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell // in the join UIs. 1126548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell long rawContactIds[] = getRawContactIdsForAggregation(contactId1, contactId2); 1127548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell if (rawContactIds == null) { 1128d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell Log.e(TAG, "Invalid arguments for joinContacts request"); 1129c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner return; 1130c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner } 11312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 1132548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); 11332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 11342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // For each pair of raw contacts, insert an aggregation exception 11352b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int i = 0; i < rawContactIds.length; i++) { 11362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int j = 0; j < rawContactIds.length; j++) { 11372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (i != j) { 11382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]); 11392b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 11402b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 11412b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 11422b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 1143d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell final ContentResolver resolver = getContentResolver(); 1144d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 1145548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell // Use the name for contactId1 as the name for the newly aggregated contact. 1146548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell final Uri contactId1Uri = ContentUris.withAppendedId( 1147548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Contacts.CONTENT_URI, contactId1); 1148548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell final Uri entityUri = Uri.withAppendedPath( 1149548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell contactId1Uri, Contacts.Entity.CONTENT_DIRECTORY); 1150548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Cursor c = resolver.query(entityUri, 1151548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell ContactEntityQuery.PROJECTION, ContactEntityQuery.SELECTION, null, null); 1152548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell if (c == null) { 1153548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Log.e(TAG, "Unable to open Contacts DB cursor"); 1154548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell showToast(R.string.contactSavedErrorToast); 1155548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell return; 1156548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1157548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell long dataIdToAddSuperPrimary = -1; 1158548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell try { 1159548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell if (c.moveToFirst()) { 1160548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell dataIdToAddSuperPrimary = c.getLong(ContactEntityQuery.DATA_ID); 1161548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1162548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } finally { 1163548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell c.close(); 1164548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1165548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell 1166548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell // Mark the name from contactId1 IS_SUPER_PRIMARY to make sure that the contact 1167548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell // display name does not change as a result of the join. 1168548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell if (dataIdToAddSuperPrimary != -1) { 11692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Builder builder = ContentProviderOperation.newUpdate( 1170548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell ContentUris.withAppendedId(Data.CONTENT_URI, dataIdToAddSuperPrimary)); 1171548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell builder.withValue(Data.IS_SUPER_PRIMARY, 1); 1172548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell builder.withValue(Data.IS_PRIMARY, 1); 11732b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov operations.add(builder.build()); 11742b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 11752b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 11762b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov boolean success = false; 11772b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Apply all aggregation exceptions as one batch 11782b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov try { 11792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov resolver.applyBatch(ContactsContract.AUTHORITY, operations); 1180886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov showToast(R.string.contactsJoinedMessage); 11812b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov success = true; 1182d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } catch (RemoteException | OperationApplicationException e) { 11832b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Log.e(TAG, "Failed to apply aggregation exception batch", e); 1184886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov showToast(R.string.contactSavedErrorToast); 11852b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 11862b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 11872b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 11882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (success) { 11892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Uri uri = RawContacts.getContactLookupUri(resolver, 11902b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0])); 11912b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov callbackIntent.setData(uri); 11922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 11933a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 11942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 11952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 1196d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell private long[] getRawContactIdsForAggregation(long[] contactIds) { 1197d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell if (contactIds == null) { 1198d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell return null; 1199d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1200d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 1201548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell final ContentResolver resolver = getContentResolver(); 1202548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell long rawContactIds[]; 1203d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 1204d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell final StringBuilder queryBuilder = new StringBuilder(); 1205d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell final String stringContactIds[] = new String[contactIds.length]; 1206d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell for (int i = 0; i < contactIds.length; i++) { 1207d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell queryBuilder.append(RawContacts.CONTACT_ID + "=?"); 1208d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell stringContactIds[i] = String.valueOf(contactIds[i]); 1209d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell if (contactIds[i] == -1) { 1210d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell return null; 1211d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1212d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell if (i == contactIds.length -1) { 1213d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell break; 1214d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1215d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell queryBuilder.append(" OR "); 1216d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1217d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 1218548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell final Cursor c = resolver.query(RawContacts.CONTENT_URI, 1219548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell JoinContactQuery.PROJECTION, 1220d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell queryBuilder.toString(), 1221d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell stringContactIds, null); 1222548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell if (c == null) { 1223548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Log.e(TAG, "Unable to open Contacts DB cursor"); 1224548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell showToast(R.string.contactSavedErrorToast); 1225548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell return null; 1226548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1227548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell try { 1228548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell if (c.getCount() < 2) { 1229d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell Log.e(TAG, "Not enough raw contacts to aggregate together."); 1230548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell return null; 1231548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1232548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell rawContactIds = new long[c.getCount()]; 1233548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell for (int i = 0; i < rawContactIds.length; i++) { 1234548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell c.moveToPosition(i); 1235548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell long rawContactId = c.getLong(JoinContactQuery._ID); 1236548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell rawContactIds[i] = rawContactId; 1237548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1238548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } finally { 1239548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell c.close(); 1240548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1241548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell return rawContactIds; 1242548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1243548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell 1244d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell private long[] getRawContactIdsForAggregation(long contactId1, long contactId2) { 1245d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell return getRawContactIdsForAggregation(new long[] {contactId1, contactId2}); 1246d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1247d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 12482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov /** 12492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation. 12502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov */ 12512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations, 12522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long rawContactId1, long rawContactId2) { 12532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Builder builder = 12542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI); 12552b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER); 12562b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1); 12572b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2); 12582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov operations.add(builder.build()); 12592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 1260886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov 1261886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov /** 1262886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov * Shows a toast on the UI thread. 1263886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov */ 1264886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov private void showToast(final int message) { 12653a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler.post(new Runnable() { 1266886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov 1267886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov @Override 1268886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov public void run() { 1269886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov Toast.makeText(ContactSaveService.this, message, Toast.LENGTH_LONG).show(); 1270886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov } 1271886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov }); 1272886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov } 12733a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 12743a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov private void deliverCallback(final Intent callbackIntent) { 12753a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler.post(new Runnable() { 12763a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 12773a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov @Override 12783a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public void run() { 12793a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallbackOnUiThread(callbackIntent); 12803a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 12813a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov }); 12823a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 12833a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 12843a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov void deliverCallbackOnUiThread(final Intent callbackIntent) { 12853a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // TODO: this assumes that if there are multiple instances of the same 12863a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // activity registered, the last one registered is the one waiting for 12873a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // the callback. Validity of this assumption needs to be verified. 1288a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson for (Listener listener : sListeners) { 1289a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson if (callbackIntent.getComponent().equals( 1290a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson ((Activity) listener).getIntent().getComponent())) { 1291a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson listener.onServiceCompleted(callbackIntent); 1292a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson return; 12933a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 12943a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 12953a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1296173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann} 1297