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 19615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shraunerimport static android.Manifest.permission.WRITE_CONTACTS; 203a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikovimport android.app.Activity; 21173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.app.IntentService; 22173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.content.ContentProviderOperation; 232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.content.ContentProviderOperation.Builder; 24caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentProviderResult; 25caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentResolver; 26e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikovimport android.content.ContentUris; 27caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentValues; 289d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikovimport android.content.Context; 29173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.content.Intent; 302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.content.OperationApplicationException; 312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.database.Cursor; 32caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.net.Uri; 33c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport android.os.Bundle; 34886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikovimport android.os.Handler; 35886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikovimport android.os.Looper; 36a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport android.os.Parcelable; 372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.os.RemoteException; 38173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.provider.ContactsContract; 392b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.provider.ContactsContract.AggregationExceptions; 401ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.GroupMembership; 41548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwellimport android.provider.ContactsContract.CommonDataKinds.StructuredName; 429d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikovimport android.provider.ContactsContract.Contacts; 43caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.Data; 44e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikovimport android.provider.ContactsContract.Groups; 45e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Leeimport android.provider.ContactsContract.PinnedPositions; 46ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelsonimport android.provider.ContactsContract.Profile; 47caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.RawContacts; 48c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoroimport android.provider.ContactsContract.RawContactsEntity; 49173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.util.Log; 502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.widget.Toast; 51173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 52d7ca03e23948c3b2d5f97ec5598d8b50e3fc0b25Chiao Chengimport com.android.contacts.common.database.ContactUpdateUtils; 530d5588da244d0992c3ff8f25d0875fdf95a8c644Chiao Chengimport com.android.contacts.common.model.AccountTypeManager; 54cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactDelta; 55cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactDeltaList; 56cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactModifier; 57428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Chengimport com.android.contacts.common.model.account.AccountWithDataSet; 58615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shraunerimport com.android.contacts.common.util.PermissionsUtil; 593e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jangimport com.android.contacts.editor.ContactEditorFragment; 60637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport com.android.contacts.util.ContactPhotoUtils; 61637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee 62e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Lists; 63e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Sets; 64e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Cheng 65c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.ArrayList; 66c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.HashSet; 67c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.List; 68c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.concurrent.CopyOnWriteArrayList; 69173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 7018ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov/** 7118ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov * A service responsible for saving changes to the content provider. 7218ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov */ 73173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannpublic class ContactSaveService extends IntentService { 74173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann private static final String TAG = "ContactSaveService"; 75173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 76a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan /** Set to true in order to view logs on content provider operations */ 77a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan private static final boolean DEBUG = false; 78a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan 79caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String ACTION_NEW_RAW_CONTACT = "newRawContact"; 80caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 81caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_ACCOUNT_NAME = "accountName"; 82caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_ACCOUNT_TYPE = "accountType"; 832b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro public static final String EXTRA_DATA_SET = "dataSet"; 84caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_CONTENT_VALUES = "contentValues"; 85caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_CALLBACK_INTENT = "callbackIntent"; 86caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 87a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String ACTION_SAVE_CONTACT = "saveContact"; 88a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String EXTRA_CONTACT_STATE = "state"; 89a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String EXTRA_SAVE_MODE = "saveMode"; 90ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson public static final String EXTRA_SAVE_IS_PROFILE = "saveIsProfile"; 9136d24d7ede42a252c82c4aa783b2231c5e2eea79Dave Santoro public static final String EXTRA_SAVE_SUCCEEDED = "saveSucceeded"; 92e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus public static final String EXTRA_UPDATED_PHOTOS = "updatedPhotos"; 93173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 941ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov public static final String ACTION_CREATE_GROUP = "createGroup"; 95e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String ACTION_RENAME_GROUP = "renameGroup"; 96e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String ACTION_DELETE_GROUP = "deleteGroup"; 972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String ACTION_UPDATE_GROUP = "updateGroup"; 98e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String EXTRA_GROUP_ID = "groupId"; 99e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String EXTRA_GROUP_LABEL = "groupLabel"; 1002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String EXTRA_RAW_CONTACTS_TO_ADD = "rawContactsToAdd"; 1012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String EXTRA_RAW_CONTACTS_TO_REMOVE = "rawContactsToRemove"; 102e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 1039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String ACTION_SET_STARRED = "setStarred"; 1047d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov public static final String ACTION_DELETE_CONTACT = "delete"; 105d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell public static final String ACTION_DELETE_MULTIPLE_CONTACTS = "deleteMultipleContacts"; 1069d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String EXTRA_CONTACT_URI = "contactUri"; 107d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell public static final String EXTRA_CONTACT_IDS = "contactIds"; 1089d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String EXTRA_STARRED_FLAG = "starred"; 1099d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 1100f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary"; 1110f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String ACTION_CLEAR_PRIMARY = "clearPrimary"; 1120f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String EXTRA_DATA_ID = "dataId"; 1130f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 1142b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String ACTION_JOIN_CONTACTS = "joinContacts"; 115d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell public static final String ACTION_JOIN_SEVERAL_CONTACTS = "joinSeveralContacts"; 1162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String EXTRA_CONTACT_ID1 = "contactId1"; 1172b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String EXTRA_CONTACT_ID2 = "contactId2"; 1182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 119683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String ACTION_SET_SEND_TO_VOICEMAIL = "sendToVoicemail"; 120683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String EXTRA_SEND_TO_VOICEMAIL_FLAG = "sendToVoicemailFlag"; 121683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 122683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String ACTION_SET_RINGTONE = "setRingtone"; 123683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String EXTRA_CUSTOM_RINGTONE = "customRingtone"; 124683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 125caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet( 126caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.MIMETYPE, 127caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.IS_PRIMARY, 128caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA1, 129caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA2, 130caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA3, 131caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA4, 132caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA5, 133caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA6, 134caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA7, 135caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA8, 136caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA9, 137caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA10, 138caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA11, 139caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA12, 140caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA13, 141caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA14, 142caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA15 143caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ); 144caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 145a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov private static final int PERSIST_TRIES = 3; 146a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 1470653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang private static final int MAX_CONTACTS_PROVIDER_BATCH_SIZE = 499; 1480653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang 1493a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public interface Listener { 1503a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public void onServiceCompleted(Intent callbackIntent); 1513a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1523a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 153a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson private static final CopyOnWriteArrayList<Listener> sListeners = 154a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson new CopyOnWriteArrayList<Listener>(); 1553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1563a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov private Handler mMainHandler; 1573a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 158173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann public ContactSaveService() { 159173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann super(TAG); 160173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann setIntentRedelivery(true); 1613a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler = new Handler(Looper.getMainLooper()); 1623a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1633a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1643a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public static void registerListener(Listener listener) { 1653a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov if (!(listener instanceof Activity)) { 1663a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov throw new ClassCastException("Only activities can be registered to" 1673a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov + " receive callback from " + ContactSaveService.class.getName()); 1683a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 169a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson sListeners.add(0, listener); 1703a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1713a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1723a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public static void unregisterListener(Listener listener) { 173a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson sListeners.remove(listener); 174173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann } 175173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 176173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann @Override 177a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public Object getSystemService(String name) { 178a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Object service = super.getSystemService(name); 179a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (service != null) { 180a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return service; 181a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 182a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 183a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return getApplicationContext().getSystemService(name); 184a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 185a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 186a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov @Override 187173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann protected void onHandleIntent(Intent intent) { 1883a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner if (intent == null) { 1893a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner Log.d(TAG, "onHandleIntent: could not handle null intent"); 1903a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner return; 1913a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner } 192615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner if (!PermissionsUtil.hasPermission(this, WRITE_CONTACTS)) { 193615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner Log.w(TAG, "No WRITE_CONTACTS permission, unable to write to CP2"); 194615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner // TODO: add more specific error string such as "Turn on Contacts 195615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner // permission to update your contacts" 196615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner showToast(R.string.contactSavedErrorToast); 197615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner return; 198615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner } 199615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner 2002f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa // Call an appropriate method. If we're sure it affects how incoming phone calls are 2012f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa // handled, then notify the fact to in-call screen. 202caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String action = intent.getAction(); 203caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov if (ACTION_NEW_RAW_CONTACT.equals(action)) { 204caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov createRawContact(intent); 205a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } else if (ACTION_SAVE_CONTACT.equals(action)) { 206a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov saveContact(intent); 2071ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } else if (ACTION_CREATE_GROUP.equals(action)) { 2081ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov createGroup(intent); 209e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } else if (ACTION_RENAME_GROUP.equals(action)) { 210e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov renameGroup(intent); 211e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } else if (ACTION_DELETE_GROUP.equals(action)) { 212e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov deleteGroup(intent); 2132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } else if (ACTION_UPDATE_GROUP.equals(action)) { 2142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan updateGroup(intent); 2159d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } else if (ACTION_SET_STARRED.equals(action)) { 2169d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov setStarred(intent); 2170f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) { 2180f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann setSuperPrimary(intent); 2190f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } else if (ACTION_CLEAR_PRIMARY.equals(action)) { 2200f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann clearPrimary(intent); 221d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell } else if (ACTION_DELETE_MULTIPLE_CONTACTS.equals(action)) { 222d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell deleteMultipleContacts(intent); 2237d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } else if (ACTION_DELETE_CONTACT.equals(action)) { 2247d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov deleteContact(intent); 2252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } else if (ACTION_JOIN_CONTACTS.equals(action)) { 2262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov joinContacts(intent); 227d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } else if (ACTION_JOIN_SEVERAL_CONTACTS.equals(action)) { 228d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell joinSeveralContacts(intent); 229683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) { 230683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson setSendToVoicemail(intent); 231683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } else if (ACTION_SET_RINGTONE.equals(action)) { 232683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson setRingtone(intent); 233caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 234caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 235caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 2369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov /** 2379d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to create a new raw contact 2389d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * using data presented as a set of ContentValues. 2399d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov */ 2409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createNewRawContactIntent(Context context, 2412b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro ArrayList<ContentValues> values, AccountWithDataSet account, 242e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 2439d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent( 2449d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov context, ContactSaveService.class); 2459d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT); 2469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov if (account != null) { 2479d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name); 2489d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type); 2492b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet); 2509d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 2519d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putParcelableArrayListExtra( 2529d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov ContactSaveService.EXTRA_CONTENT_VALUES, values); 2539d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 2549d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // Callback intent will be invoked by the service once the new contact is 2559d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // created. The service will put the URI of the new contact as "data" on 2569d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // the callback intent. 2579d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 2589d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov callbackIntent.setAction(callbackAction); 2599d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 2609d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov return serviceIntent; 2619d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 2629d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 263caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov private void createRawContact(Intent intent) { 264caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME); 265caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE); 2662b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String dataSet = intent.getStringExtra(EXTRA_DATA_SET); 267caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES); 268caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 269caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 270caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); 271caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) 272caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValue(RawContacts.ACCOUNT_NAME, accountName) 273caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValue(RawContacts.ACCOUNT_TYPE, accountType) 2742b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro .withValue(RawContacts.DATA_SET, dataSet) 275caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .build()); 276caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 277caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov int size = valueList.size(); 278caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov for (int i = 0; i < size; i++) { 279caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentValues values = valueList.get(i); 280caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov values.keySet().retainAll(ALLOWED_DATA_COLUMNS); 281caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) 282caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValueBackReference(Data.RAW_CONTACT_ID, 0) 283caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValues(values) 284caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .build()); 285caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 286caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 287caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentResolver resolver = getContentResolver(); 288caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentProviderResult[] results; 289caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov try { 290caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov results = resolver.applyBatch(ContactsContract.AUTHORITY, operations); 291caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } catch (Exception e) { 292caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov throw new RuntimeException("Failed to store new contact", e); 293caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 294caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 295caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Uri rawContactUri = results[0].uri; 296caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri)); 297caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 2983a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 299caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 300caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 301caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov /** 302a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov * Creates an intent that can be sent to this service to create a new raw contact 303a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov * using data presented as a set of ContentValues. 304e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * This variant is more convenient to use when there is only one photo that can 305e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * possibly be updated, as in the Contact Details screen. 306e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @param rawContactId identifies a writable raw-contact whose photo is to be updated. 307e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @param updatedPhotoPath denotes a temporary file containing the contact's new photo. 308a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov */ 309851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu public static Intent createSaveContactIntent(Context context, RawContactDeltaList state, 310e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus String saveModeExtraKey, int saveMode, boolean isProfile, 311e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction, long rawContactId, 312637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee Uri updatedPhotoPath) { 313e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus Bundle bundle = new Bundle(); 314637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee bundle.putParcelable(String.valueOf(rawContactId), updatedPhotoPath); 315e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus return createSaveContactIntent(context, state, saveModeExtraKey, saveMode, isProfile, 3163e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang callbackActivity, callbackAction, bundle, /* backPressed =*/ false); 317e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 318e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 319e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus /** 320e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * Creates an intent that can be sent to this service to create a new raw contact 321e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * using data presented as a set of ContentValues. 322e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * This variant is used when multiple contacts' photos may be updated, as in the 323e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * Contact Editor. 324e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @param updatedPhotos maps each raw-contact's ID to the file-path of the new photo. 3253e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang * @param backPressed whether the save was initiated as a result of a back button press 3263e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang * or because the framework stopped the editor Activity 327e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus */ 328851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu public static Intent createSaveContactIntent(Context context, RawContactDeltaList state, 329e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus String saveModeExtraKey, int saveMode, boolean isProfile, 330e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction, 3313e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang Bundle updatedPhotos, boolean backPressed) { 332a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Intent serviceIntent = new Intent( 333a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov context, ContactSaveService.class); 334a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT); 335a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state); 336ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson serviceIntent.putExtra(EXTRA_SAVE_IS_PROFILE, isProfile); 337e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus if (updatedPhotos != null) { 338e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus serviceIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos); 339e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 340a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 341e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus if (callbackActivity != null) { 342e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // Callback intent will be invoked by the service once the contact is 343e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // saved. The service will put the URI of the new contact as "data" on 344e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // the callback intent. 345e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Intent callbackIntent = new Intent(context, callbackActivity); 346e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.putExtra(saveModeExtraKey, saveMode); 347e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.setAction(callbackAction); 3481e8801bc9bc60bdd1c95f582c460590272cfad64Walter Jang if (updatedPhotos != null) { 3491e8801bc9bc60bdd1c95f582c460590272cfad64Walter Jang callbackIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos); 3501e8801bc9bc60bdd1c95f582c460590272cfad64Walter Jang } 3513e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang callbackIntent.putExtra(ContactEditorFragment.INTENT_EXTRA_SAVE_BACK_PRESSED, 3523e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang backPressed); 353e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 354e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus } 355a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return serviceIntent; 356a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 357a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 358a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov private void saveContact(Intent intent) { 359851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu RawContactDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE); 360ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false); 361e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus Bundle updatedPhotos = intent.getParcelableExtra(EXTRA_UPDATED_PHOTOS); 362a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 3630809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner if (state == null) { 3640809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner Log.e(TAG, "Invalid arguments for saveContact request"); 3650809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner return; 3660809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner } 3670809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner 368a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Trim any empty fields, and RawContacts, before persisting 369a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this); 370851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu RawContactModifier.trimEmpty(state, accountTypes); 371a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 372a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Uri lookupUri = null; 373a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 374a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ContentResolver resolver = getContentResolver(); 375e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus boolean succeeded = false; 376a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 377ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // Keep track of the id of a newly raw-contact (if any... there can be at most one). 378ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus long insertedRawContactId = -1; 379ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 380a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Attempt to persist changes 381a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov int tries = 0; 382a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov while (tries++ < PERSIST_TRIES) { 383a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov try { 384a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Build operations and try applying 385a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ArrayList<ContentProviderOperation> diff = state.buildDiff(); 386a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan if (DEBUG) { 387a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan Log.v(TAG, "Content Provider Operations:"); 388a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan for (ContentProviderOperation operation : diff) { 389a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan Log.v(TAG, operation.toString()); 390a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan } 391a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan } 392a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan 393a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov ContentProviderResult[] results = null; 394a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (!diff.isEmpty()) { 395a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov results = resolver.applyBatch(ContactsContract.AUTHORITY, diff); 396511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner if (results == null) { 397511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner Log.w(TAG, "Resolver.applyBatch failed in saveContacts"); 398511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner // Retry save 399511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner continue; 400511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner } 401a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 402a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 403a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final long rawContactId = getRawContactId(state, diff, results); 404a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (rawContactId == -1) { 405a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov throw new IllegalStateException("Could not determine RawContact ID after save"); 406a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 407ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // We don't have to check to see if the value is still -1. If we reach here, 408ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // the previous loop iteration didn't succeed, so any ID that we obtained is bogus. 409ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus insertedRawContactId = getInsertedRawContactId(diff, results); 4107c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro if (isProfile) { 4117c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro // Since the profile supports local raw contacts, which may have been completely 4127c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro // removed if all information was removed, we need to do a special query to 4137c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro // get the lookup URI for the profile contact (if it still exists). 4147c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro Cursor c = resolver.query(Profile.CONTENT_URI, 4157c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro new String[] {Contacts._ID, Contacts.LOOKUP_KEY}, 4167c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro null, null, null); 417e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner if (c == null) { 418e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner continue; 419e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner } 4207c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro try { 421162b7e34fb581d0fa279957af5136d190c40759fErik if (c.moveToFirst()) { 422162b7e34fb581d0fa279957af5136d190c40759fErik final long contactId = c.getLong(0); 423162b7e34fb581d0fa279957af5136d190c40759fErik final String lookupKey = c.getString(1); 424162b7e34fb581d0fa279957af5136d190c40759fErik lookupUri = Contacts.getLookupUri(contactId, lookupKey); 425162b7e34fb581d0fa279957af5136d190c40759fErik } 4267c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } finally { 4277c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro c.close(); 4287c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } 4297c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } else { 4307c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, 4317c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro rawContactId); 4327c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri); 4337c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro } 434e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner if (lookupUri != null) { 435e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner Log.v(TAG, "Saved contact. New URI: " + lookupUri); 436e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner } 437e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 438e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus // We can change this back to false later, if we fail to save the contact photo. 439e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus succeeded = true; 440a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov break; 441a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 442a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } catch (RemoteException e) { 443a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Something went wrong, bail without success 444a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Log.e(TAG, "Problem persisting user edits", e); 445a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov break; 446a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 44757fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner } catch (IllegalArgumentException e) { 44857fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner // This is thrown by applyBatch on malformed requests 44957fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner Log.e(TAG, "Problem persisting user edits", e); 45057fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner showToast(R.string.contactSavedErrorToast); 45157fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner break; 45257fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner 453a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } catch (OperationApplicationException e) { 454a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Version consistency failed, re-parent change and try again 455a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString()); 456a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN("); 457a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov boolean first = true; 458a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final int count = state.size(); 459a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov for (int i = 0; i < count; i++) { 460a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Long rawContactId = state.getRawContactId(i); 461a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (rawContactId != null && rawContactId != -1) { 462a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (!first) { 463a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(','); 464a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 465a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(rawContactId); 466a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov first = false; 467a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 468a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 469a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(")"); 470a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 471a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (first) { 4723b6c628fc9e717a41c2954b1101c3a04ad382c55Brian Attwell throw new IllegalStateException( 4733b6c628fc9e717a41c2954b1101c3a04ad382c55Brian Attwell "Version consistency failed for a new contact", e); 474a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 475a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 476851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu final RawContactDeltaList newState = RawContactDeltaList.fromQuery( 477c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro isProfile 478c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro ? RawContactsEntity.PROFILE_CONTENT_URI 479c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro : RawContactsEntity.CONTENT_URI, 480c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro resolver, sb.toString(), null, null); 481851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu state = RawContactDeltaList.mergeAfter(newState, state); 482c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro 483c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro // Update the new state to use profile URIs if appropriate. 484c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro if (isProfile) { 485851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu for (RawContactDelta delta : state) { 486c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro delta.setProfileQueryUri(); 487c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro } 488c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro } 489a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 490a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 491a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 492e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus // Now save any updated photos. We do this at the end to ensure that 493e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus // the ContactProvider already knows about newly-created contacts. 494e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus if (updatedPhotos != null) { 495e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus for (String key : updatedPhotos.keySet()) { 496637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee Uri photoUri = updatedPhotos.getParcelable(key); 497e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus long rawContactId = Long.parseLong(key); 498ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 499ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // If the raw-contact ID is negative, we are saving a new raw-contact; 500ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus // replace the bogus ID with the new one that we actually saved the contact at. 501ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus if (rawContactId < 0) { 502ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus rawContactId = insertedRawContactId; 503ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } 504ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 505511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner // If the save failed, insertedRawContactId will be -1 506511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner if (rawContactId < 0 || !saveUpdatedPhoto(rawContactId, photoUri)) { 507511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner succeeded = false; 508511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner } 509e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 510e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 511e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 512e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 513e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus if (callbackIntent != null) { 514e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus if (succeeded) { 515e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // Mark the intent to indicate that the save was successful (even if the lookup URI 516e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // is now null). For local contacts or the local profile, it's possible that the 517e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus // save triggered removal of the contact, so no lookup URI would exist.. 518e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.putExtra(EXTRA_SAVE_SUCCEEDED, true); 519e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus } 520e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus callbackIntent.setData(lookupUri); 521e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus deliverCallback(callbackIntent); 522e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 523a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 524a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 525e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus /** 526e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * Save updated photo for the specified raw-contact. 527e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus * @return true for success, false for failure 528e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus */ 529637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee private boolean saveUpdatedPhoto(long rawContactId, Uri photoUri) { 530ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final Uri outputUri = Uri.withAppendedPath( 531e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), 532e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus RawContacts.DisplayPhoto.CONTENT_DIRECTORY); 533e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 534637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee return ContactPhotoUtils.savePhotoFromUriToUri(this, photoUri, outputUri, true); 535e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus } 536e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus 537ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus /** 538ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus * Find the ID of an existing or newly-inserted raw-contact. If none exists, return -1. 539ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus */ 540851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu private long getRawContactId(RawContactDeltaList state, 541a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ArrayList<ContentProviderOperation> diff, 542a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ContentProviderResult[] results) { 543ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus long existingRawContactId = state.findRawContactId(); 544ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus if (existingRawContactId != -1) { 545ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus return existingRawContactId; 546a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 547a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 548ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus return getInsertedRawContactId(diff, results); 549ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus } 550ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus 551ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus /** 552ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus * Find the ID of a newly-inserted raw-contact. If none exists, return -1. 553ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus */ 554ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus private long getInsertedRawContactId( 555ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final ArrayList<ContentProviderOperation> diff, 556ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus final ContentProviderResult[] results) { 557568f4e72711908455ccd20fbb04c1017b10d7e1cJay Shrauner if (results == null) { 558568f4e72711908455ccd20fbb04c1017b10d7e1cJay Shrauner return -1; 559568f4e72711908455ccd20fbb04c1017b10d7e1cJay Shrauner } 560a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final int diffSize = diff.size(); 5613d7edc3ef4f2521639bae91c93a8238e5a9509c1Jay Shrauner final int numResults = results.length; 5623d7edc3ef4f2521639bae91c93a8238e5a9509c1Jay Shrauner for (int i = 0; i < diffSize && i < numResults; i++) { 563a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov ContentProviderOperation operation = diff.get(i); 56413f94e1c52f553a7137e6730c09c4d28c3f54c9fBrian Attwell if (operation.isInsert() && operation.getUri().getEncodedPath().contains( 565a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov RawContacts.CONTENT_URI.getEncodedPath())) { 566a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return ContentUris.parseId(results[i].uri); 567a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 568a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 569a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return -1; 570a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 571a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 572a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov /** 573717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * Creates an intent that can be sent to this service to create a new group as 574717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * well as add new members at the same time. 575717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * 576717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param context of the application 577717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param account in which the group should be created 578717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param label is the name of the group (cannot be null) 579717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param rawContactsToAdd is an array of raw contact IDs for contacts that 580717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * should be added to the group 581717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param callbackActivity is the activity to send the callback intent to 582717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param callbackAction is the intent action for the callback intent 583caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov */ 5842b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro public static Intent createNewGroupIntent(Context context, AccountWithDataSet account, 585e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus String label, long[] rawContactsToAdd, Class<? extends Activity> callbackActivity, 586717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan String callbackAction) { 5879d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 5889d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP); 5899d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type); 5909d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name); 5912b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet); 5929d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label); 593717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd); 594caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 5959d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // Callback intent will be invoked by the service once the new group is 596717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // created. 5979d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 5989d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov callbackIntent.setAction(callbackAction); 5991ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 6009d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 6011ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov return serviceIntent; 6021ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 6031ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 6041ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov private void createGroup(Intent intent) { 6052b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE); 6062b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME); 6072b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String dataSet = intent.getStringExtra(EXTRA_DATA_SET); 6082b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 609717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD); 6101ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 6111ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov ContentValues values = new ContentValues(); 6121ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.ACCOUNT_TYPE, accountType); 6131ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.ACCOUNT_NAME, accountName); 6142b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro values.put(Groups.DATA_SET, dataSet); 6151ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.TITLE, label); 6161ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 617717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final ContentResolver resolver = getContentResolver(); 618717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 619717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Create the new group 620717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values); 621717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 622717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // If there's no URI, then the insertion failed. Abort early because group members can't be 623717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // added if the group doesn't exist 6241ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov if (groupUri == null) { 625717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan Log.e(TAG, "Couldn't create group with label " + label); 6261ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov return; 6271ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 6281ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 629717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Add new group members 630717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri)); 631717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 632717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // TODO: Move this into the contact editor where it belongs. This needs to be integrated 633717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // with the way other intent extras that are passed to the {@link ContactEditorActivity}. 6341ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.clear(); 6351ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 6361ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri)); 6371ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 6381ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 639c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setData(groupUri); 640717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // TODO: This can be taken out when the above TODO is addressed 6411ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values)); 6423a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 6431ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 6441ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 6451ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov /** 6469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to rename a group. 6471ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov */ 648c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel, 649e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 6509d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 6519d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP); 6529d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 6539d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel); 654c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 655c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan // Callback intent will be invoked by the service once the group is renamed. 656c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan Intent callbackIntent = new Intent(context, callbackActivity); 657c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setAction(callbackAction); 658c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 659c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 660caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov return serviceIntent; 661caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 662e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 663e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov private void renameGroup(Intent intent) { 664e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 665e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 666e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 667e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov if (groupId == -1) { 668e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov Log.e(TAG, "Invalid arguments for renameGroup request"); 669e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return; 670e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 671e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 672e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov ContentValues values = new ContentValues(); 673e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov values.put(Groups.TITLE, label); 674c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId); 675c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan getContentResolver().update(groupUri, values, null, null); 676c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 677c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 678c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setData(groupUri); 679c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan deliverCallback(callbackIntent); 680e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 681e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 682e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov /** 6839d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to delete a group. 684e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov */ 6859d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createGroupDeletionIntent(Context context, long groupId) { 6869d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 6879d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP); 688e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 689e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return serviceIntent; 690e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 691e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 692e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov private void deleteGroup(Intent intent) { 693e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 694e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov if (groupId == -1) { 695e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov Log.e(TAG, "Invalid arguments for deleteGroup request"); 696e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return; 697e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 698e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 699e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov getContentResolver().delete( 700e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null); 701e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 702e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 703e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov /** 7042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * Creates an intent that can be sent to this service to rename a group as 7052d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * well as add and remove members from the group. 7062d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * 7072d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param context of the application 7082d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param groupId of the group that should be modified 7092d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param newLabel is the updated name of the group (can be null if the name 7102d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * should not be updated) 7112d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param rawContactsToAdd is an array of raw contact IDs for contacts that 7122d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * should be added to the group 7132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param rawContactsToRemove is an array of raw contact IDs for contacts 7142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * that should be removed from the group 7152d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param callbackActivity is the activity to send the callback intent to 7162d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param callbackAction is the intent action for the callback intent 7172d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan */ 7182d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel, 7192d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToAdd, long[] rawContactsToRemove, 720e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus Class<? extends Activity> callbackActivity, String callbackAction) { 7212d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Intent serviceIntent = new Intent(context, ContactSaveService.class); 7222d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP); 7232d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 7242d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel); 7252d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd); 7262d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_REMOVE, 7272d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactsToRemove); 7282d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7292d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Callback intent will be invoked by the service once the group is updated 7302d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Intent callbackIntent = new Intent(context, callbackActivity); 7312d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan callbackIntent.setAction(callbackAction); 7322d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 7332d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan return serviceIntent; 7352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7362d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7372d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan private void updateGroup(Intent intent) { 7382d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 7392d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 7402d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD); 7412d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToRemove = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_REMOVE); 7422d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7432d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (groupId == -1) { 7442d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.e(TAG, "Invalid arguments for updateGroup request"); 7452d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan return; 7462d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7472d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7482d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentResolver resolver = getContentResolver(); 7492d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId); 7502d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7512d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Update group name if necessary 7522d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (label != null) { 7532d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan ContentValues values = new ContentValues(); 7542d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan values.put(Groups.TITLE, label); 755717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan resolver.update(groupUri, values, null, null); 7562d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7572d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 758717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Add and remove members if necessary 759717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan addMembersToGroup(resolver, rawContactsToAdd, groupId); 760717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan removeMembersFromGroup(resolver, rawContactsToRemove, groupId); 761717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 762717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 763717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan callbackIntent.setData(groupUri); 764717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan deliverCallback(callbackIntent); 765717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 766717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 76718958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann private static void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd, 768717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan long groupId) { 769717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan if (rawContactsToAdd == null) { 770717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan return; 771717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 7722d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (long rawContactId : rawContactsToAdd) { 7732d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan try { 7742d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ArrayList<ContentProviderOperation> rawContactOperations = 7752d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new ArrayList<ContentProviderOperation>(); 7762d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7772d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Build an assert operation to ensure the contact is not already in the group 7782d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentProviderOperation.Builder assertBuilder = ContentProviderOperation 7792d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan .newAssertQuery(Data.CONTENT_URI); 7802d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan assertBuilder.withSelection(Data.RAW_CONTACT_ID + "=? AND " + 7812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?", 7822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new String[] { String.valueOf(rawContactId), 7832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)}); 7842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan assertBuilder.withExpectedCount(0); 7852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactOperations.add(assertBuilder.build()); 7862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7872d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Build an insert operation to add the contact to the group 7882d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentProviderOperation.Builder insertBuilder = ContentProviderOperation 7892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan .newInsert(Data.CONTENT_URI); 7902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(Data.RAW_CONTACT_ID, rawContactId); 7912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 7922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(GroupMembership.GROUP_ROW_ID, groupId); 7932d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactOperations.add(insertBuilder.build()); 7942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 7952d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (DEBUG) { 7962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (ContentProviderOperation operation : rawContactOperations) { 7972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.v(TAG, operation.toString()); 7982d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 7992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 8002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 8012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Apply batch 8022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (!rawContactOperations.isEmpty()) { 80318958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations); 8042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 8052d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } catch (RemoteException e) { 8062d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Something went wrong, bail without success 8072d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.e(TAG, "Problem persisting user edits for raw contact ID " + 8082d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(rawContactId), e); 8092d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } catch (OperationApplicationException e) { 8102d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // The assert could have failed because the contact is already in the group, 8112d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // just continue to the next contact 8122d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.w(TAG, "Assert failed in adding raw contact ID " + 8132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(rawContactId) + ". Already exists in group " + 8142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(groupId), e); 8152d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 8162d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 817717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 8182d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 81918958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann private static void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove, 820717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan long groupId) { 821717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan if (rawContactsToRemove == null) { 822717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan return; 823717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 8242d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (long rawContactId : rawContactsToRemove) { 8252d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Apply the delete operation on the data row for the given raw contact's 8262d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // membership in the given group. If no contact matches the provided selection, then 8272d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // nothing will be done. Just continue to the next contact. 82818958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann resolver.delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " + 8292d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?", 8302d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new String[] { String.valueOf(rawContactId), 8312d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)}); 8322d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 8332d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 8342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 8352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan /** 8369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to star or un-star a contact. 837e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov */ 8389d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) { 8399d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 8409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED); 8419d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 8429d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value); 8439d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 844e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return serviceIntent; 845e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 8469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 8479d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov private void setStarred(Intent intent) { 8489d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 8499d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false); 8509d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov if (contactUri == null) { 8519d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Log.e(TAG, "Invalid arguments for setStarred request"); 8529d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov return; 8539d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 8549d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 8559d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov final ContentValues values = new ContentValues(1); 8569d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov values.put(Contacts.STARRED, value); 8579d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov getContentResolver().update(contactUri, values, null, null); 858e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee 859e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee // Undemote the contact if necessary 860e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee final Cursor c = getContentResolver().query(contactUri, new String[] {Contacts._ID}, 861e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee null, null, null); 862c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner if (c == null) { 863c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner return; 864c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner } 865e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee try { 866e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee if (c.moveToFirst()) { 867e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee final long id = c.getLong(0); 868bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee 869bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee // Don't bother undemoting if this contact is the user's profile. 870bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee if (id < Profile.MIN_ID) { 8712d88efaf9efa059c70783acffb6ec3055e1b883bBrian Attwell PinnedPositions.undemote(getContentResolver(), id); 872bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee } 873e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee } 874e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee } finally { 875e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee c.close(); 876e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee } 8779d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 8780f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 8790f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann /** 880683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson * Creates an intent that can be sent to this service to set the redirect to voicemail. 881683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson */ 882683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static Intent createSetSendToVoicemail(Context context, Uri contactUri, 883683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson boolean value) { 884683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Intent serviceIntent = new Intent(context, ContactSaveService.class); 885683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.setAction(ContactSaveService.ACTION_SET_SEND_TO_VOICEMAIL); 886683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 887683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_SEND_TO_VOICEMAIL_FLAG, value); 888683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 889683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return serviceIntent; 890683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 891683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 892683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson private void setSendToVoicemail(Intent intent) { 893683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 894683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson boolean value = intent.getBooleanExtra(EXTRA_SEND_TO_VOICEMAIL_FLAG, false); 895683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson if (contactUri == null) { 896683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Log.e(TAG, "Invalid arguments for setRedirectToVoicemail"); 897683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return; 898683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 899683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 900683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson final ContentValues values = new ContentValues(1); 901683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson values.put(Contacts.SEND_TO_VOICEMAIL, value); 902683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson getContentResolver().update(contactUri, values, null, null); 903683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 904683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 905683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson /** 906683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson * Creates an intent that can be sent to this service to save the contact's ringtone. 907683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson */ 908683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static Intent createSetRingtone(Context context, Uri contactUri, 909683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson String value) { 910683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Intent serviceIntent = new Intent(context, ContactSaveService.class); 911683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.setAction(ContactSaveService.ACTION_SET_RINGTONE); 912683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 913683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CUSTOM_RINGTONE, value); 914683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 915683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return serviceIntent; 916683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 917683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 918683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson private void setRingtone(Intent intent) { 919683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 920683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson String value = intent.getStringExtra(EXTRA_CUSTOM_RINGTONE); 921683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson if (contactUri == null) { 922683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Log.e(TAG, "Invalid arguments for setRingtone"); 923683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return; 924683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 925683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson ContentValues values = new ContentValues(1); 926683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson values.put(Contacts.CUSTOM_RINGTONE, value); 927683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson getContentResolver().update(contactUri, values, null, null); 928683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 929683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 930683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson /** 9310f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * Creates an intent that sets the selected data item as super primary (default) 9320f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann */ 9330f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static Intent createSetSuperPrimaryIntent(Context context, long dataId) { 9340f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Intent serviceIntent = new Intent(context, ContactSaveService.class); 9350f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY); 9360f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId); 9370f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return serviceIntent; 9380f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9390f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9400f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann private void setSuperPrimary(Intent intent) { 9410f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1); 9420f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann if (dataId == -1) { 9430f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Log.e(TAG, "Invalid arguments for setSuperPrimary request"); 9440f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return; 9450f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9460f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 947d7ca03e23948c3b2d5f97ec5598d8b50e3fc0b25Chiao Cheng ContactUpdateUtils.setSuperPrimary(this, dataId); 9480f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9490f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9500f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann /** 9510f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * Creates an intent that clears the primary flag of all data items that belong to the same 9520f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * raw_contact as the given data item. Will only clear, if the data item was primary before 9530f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * this call 9540f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann */ 9550f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static Intent createClearPrimaryIntent(Context context, long dataId) { 9560f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Intent serviceIntent = new Intent(context, ContactSaveService.class); 9570f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY); 9580f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId); 9590f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return serviceIntent; 9600f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9610f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9620f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann private void clearPrimary(Intent intent) { 9630f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1); 9640f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann if (dataId == -1) { 9650f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Log.e(TAG, "Invalid arguments for clearPrimary request"); 9660f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return; 9670f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9680f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9690f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann // Update the primary values in the data record. 9700f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann ContentValues values = new ContentValues(1); 9710f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_SUPER_PRIMARY, 0); 9720f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_PRIMARY, 0); 9730f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 9740f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), 9750f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values, null, null); 9760f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 9777d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 9787d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov /** 9797d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov * Creates an intent that can be sent to this service to delete a contact. 9807d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov */ 9817d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov public static Intent createDeleteContactIntent(Context context, Uri contactUri) { 9827d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 9837d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT); 9847d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 9857d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov return serviceIntent; 9867d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 9877d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 988d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell /** 989d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell * Creates an intent that can be sent to this service to delete multiple contacts. 990d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell */ 991d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell public static Intent createDeleteMultipleContactsIntent(Context context, 992d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell long[] contactIds) { 993d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell Intent serviceIntent = new Intent(context, ContactSaveService.class); 994d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell serviceIntent.setAction(ContactSaveService.ACTION_DELETE_MULTIPLE_CONTACTS); 995d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_IDS, contactIds); 996d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell return serviceIntent; 997d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell } 998d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell 9997d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov private void deleteContact(Intent intent) { 10007d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 10017d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov if (contactUri == null) { 10027d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Log.e(TAG, "Invalid arguments for deleteContact request"); 10037d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov return; 10047d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 10057d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 10067d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov getContentResolver().delete(contactUri, null, null); 10077d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 10082b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 1009d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell private void deleteMultipleContacts(Intent intent) { 1010d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell final long[] contactIds = intent.getLongArrayExtra(EXTRA_CONTACT_IDS); 1011d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell if (contactIds == null) { 1012d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell Log.e(TAG, "Invalid arguments for deleteMultipleContacts request"); 1013d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell return; 1014d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell } 1015d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell for (long contactId : contactIds) { 1016d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell final Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); 1017d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell getContentResolver().delete(contactUri, null, null); 1018d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell } 1019e986c6b5954f7b9fea58cfb11c86b61d3defa271Brian Attwell showToast(R.string.contacts_deleted_toast); 1020d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell } 1021d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell 10222b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov /** 10232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov * Creates an intent that can be sent to this service to join two contacts. 1024d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell * The resulting contact uses the name from {@param contactId1} if possible. 10252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov */ 10262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static Intent createJoinContactsIntent(Context context, long contactId1, 1027d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell long contactId2, Class<? extends Activity> callbackActivity, String callbackAction) { 10282b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 10292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS); 10302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1); 10312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2); 10322b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Callback intent will be invoked by the service once the contacts are joined. 10342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 10352b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov callbackIntent.setAction(callbackAction); 10362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 10372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov return serviceIntent; 10392b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10402b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 1041d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell /** 1042d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell * Creates an intent to join all raw contacts inside {@param contactIds}'s contacts. 1043d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell * No special attention is paid to where the resulting contact's name is taken from. 1044d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell */ 1045d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell public static Intent createJoinSeveralContactsIntent(Context context, long[] contactIds) { 1046d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell Intent serviceIntent = new Intent(context, ContactSaveService.class); 1047d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell serviceIntent.setAction(ContactSaveService.ACTION_JOIN_SEVERAL_CONTACTS); 1048d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_IDS, contactIds); 1049d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell return serviceIntent; 1050d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1051d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 10522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov private interface JoinContactQuery { 10542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov String[] PROJECTION = { 10552b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts._ID, 10562b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts.CONTACT_ID, 10572b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts.DISPLAY_NAME_SOURCE, 10582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov }; 10592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 10602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int _ID = 0; 10612b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int CONTACT_ID = 1; 1062548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell int DISPLAY_NAME_SOURCE = 2; 1063548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1064548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell 1065548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell private interface ContactEntityQuery { 1066548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell String[] PROJECTION = { 1067548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Contacts.Entity.DATA_ID, 1068548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Contacts.Entity.CONTACT_ID, 1069548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Contacts.Entity.IS_SUPER_PRIMARY, 1070548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell }; 1071548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell String SELECTION = Data.MIMETYPE + " = '" + StructuredName.CONTENT_ITEM_TYPE + "'" + 1072548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell " AND " + StructuredName.DISPLAY_NAME + "=" + Contacts.DISPLAY_NAME + 1073548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell " AND " + StructuredName.DISPLAY_NAME + " IS NOT NULL " + 1074548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell " AND " + StructuredName.DISPLAY_NAME + " != '' "; 1075548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell 1076548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell int DATA_ID = 0; 1077548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell int CONTACT_ID = 1; 1078548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell int IS_SUPER_PRIMARY = 2; 10792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10802b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 1081d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell private void joinSeveralContacts(Intent intent) { 1082d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell final long[] contactIds = intent.getLongArrayExtra(EXTRA_CONTACT_IDS); 1083548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell 1084d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell // Load raw contact IDs for all contacts involved. 1085d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell long rawContactIds[] = getRawContactIdsForAggregation(contactIds); 1086d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell if (rawContactIds == null) { 1087d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell Log.e(TAG, "Invalid arguments for joinSeveralContacts request"); 10882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov return; 10892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 10902b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 1091d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell // For each pair of raw contacts, insert an aggregation exception 10922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov final ContentResolver resolver = getContentResolver(); 10930653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang // The maximum number of operations per batch (aka yield point) is 500. See b/22480225 10940653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang final int batchSize = MAX_CONTACTS_PROVIDER_BATCH_SIZE; 10950653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang final ArrayList<ContentProviderOperation> operations = new ArrayList<>(batchSize); 1096d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell for (int i = 0; i < rawContactIds.length; i++) { 1097d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell for (int j = 0; j < rawContactIds.length; j++) { 1098d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell if (i != j) { 1099d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]); 1100d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 11010653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang // Before we get to 500 we need to flush the operations list 11020653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang if (operations.size() > 0 && operations.size() % batchSize == 0) { 11030653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang if (!applyJoinOperations(resolver, operations)) { 11040653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang return; 11050653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang } 11060653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang operations.clear(); 11070653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang } 1108d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1109d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 11100653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang if (operations.size() > 0 && !applyJoinOperations(resolver, operations)) { 11110653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang return; 11120653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang } 11130653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang showToast(R.string.contactsJoinedMessage); 11140653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang } 1115d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 11160653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang /** Returns true if the batch was successfully applied and false otherwise. */ 11170653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang private boolean applyJoinOperations(ContentResolver resolver, 11180653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang ArrayList<ContentProviderOperation> operations) { 1119d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell try { 1120d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell resolver.applyBatch(ContactsContract.AUTHORITY, operations); 11210653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang return true; 1122d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } catch (RemoteException | OperationApplicationException e) { 1123d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell Log.e(TAG, "Failed to apply aggregation exception batch", e); 1124d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell showToast(R.string.contactSavedErrorToast); 11250653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang return false; 1126d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1127d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1128d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 1129d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 1130d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell private void joinContacts(Intent intent) { 1131d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1); 1132d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1); 11332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 11342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Load raw contact IDs for all raw contacts involved - currently edited and selected 1135548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell // in the join UIs. 1136548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell long rawContactIds[] = getRawContactIdsForAggregation(contactId1, contactId2); 1137548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell if (rawContactIds == null) { 1138d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell Log.e(TAG, "Invalid arguments for joinContacts request"); 1139c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner return; 1140c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner } 11412b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 1142548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); 11432b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 11442b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // For each pair of raw contacts, insert an aggregation exception 11452b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int i = 0; i < rawContactIds.length; i++) { 11462b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int j = 0; j < rawContactIds.length; j++) { 11472b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (i != j) { 11482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]); 11492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 11502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 11512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 11522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 1153d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell final ContentResolver resolver = getContentResolver(); 1154d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 1155548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell // Use the name for contactId1 as the name for the newly aggregated contact. 1156548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell final Uri contactId1Uri = ContentUris.withAppendedId( 1157548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Contacts.CONTENT_URI, contactId1); 1158548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell final Uri entityUri = Uri.withAppendedPath( 1159548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell contactId1Uri, Contacts.Entity.CONTENT_DIRECTORY); 1160548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Cursor c = resolver.query(entityUri, 1161548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell ContactEntityQuery.PROJECTION, ContactEntityQuery.SELECTION, null, null); 1162548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell if (c == null) { 1163548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Log.e(TAG, "Unable to open Contacts DB cursor"); 1164548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell showToast(R.string.contactSavedErrorToast); 1165548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell return; 1166548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1167548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell long dataIdToAddSuperPrimary = -1; 1168548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell try { 1169548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell if (c.moveToFirst()) { 1170548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell dataIdToAddSuperPrimary = c.getLong(ContactEntityQuery.DATA_ID); 1171548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1172548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } finally { 1173548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell c.close(); 1174548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1175548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell 1176548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell // Mark the name from contactId1 IS_SUPER_PRIMARY to make sure that the contact 1177548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell // display name does not change as a result of the join. 1178548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell if (dataIdToAddSuperPrimary != -1) { 11792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Builder builder = ContentProviderOperation.newUpdate( 1180548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell ContentUris.withAppendedId(Data.CONTENT_URI, dataIdToAddSuperPrimary)); 1181548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell builder.withValue(Data.IS_SUPER_PRIMARY, 1); 1182548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell builder.withValue(Data.IS_PRIMARY, 1); 11832b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov operations.add(builder.build()); 11842b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 11852b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 11862b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov boolean success = false; 11872b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Apply all aggregation exceptions as one batch 11882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov try { 11892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov resolver.applyBatch(ContactsContract.AUTHORITY, operations); 1190886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov showToast(R.string.contactsJoinedMessage); 11912b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov success = true; 1192d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } catch (RemoteException | OperationApplicationException e) { 11932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Log.e(TAG, "Failed to apply aggregation exception batch", e); 1194886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov showToast(R.string.contactSavedErrorToast); 11952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 11962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 11972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 11982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (success) { 11992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Uri uri = RawContacts.getContactLookupUri(resolver, 12002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0])); 12012b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov callbackIntent.setData(uri); 12022b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 12033a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 12042b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 12052b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 1206d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell private long[] getRawContactIdsForAggregation(long[] contactIds) { 1207d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell if (contactIds == null) { 1208d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell return null; 1209d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1210d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 1211548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell final ContentResolver resolver = getContentResolver(); 1212548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell long rawContactIds[]; 1213d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 1214d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell final StringBuilder queryBuilder = new StringBuilder(); 1215d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell final String stringContactIds[] = new String[contactIds.length]; 1216d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell for (int i = 0; i < contactIds.length; i++) { 1217d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell queryBuilder.append(RawContacts.CONTACT_ID + "=?"); 1218d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell stringContactIds[i] = String.valueOf(contactIds[i]); 1219d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell if (contactIds[i] == -1) { 1220d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell return null; 1221d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1222d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell if (i == contactIds.length -1) { 1223d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell break; 1224d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1225d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell queryBuilder.append(" OR "); 1226d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1227d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 1228548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell final Cursor c = resolver.query(RawContacts.CONTENT_URI, 1229548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell JoinContactQuery.PROJECTION, 1230d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell queryBuilder.toString(), 1231d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell stringContactIds, null); 1232548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell if (c == null) { 1233548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell Log.e(TAG, "Unable to open Contacts DB cursor"); 1234548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell showToast(R.string.contactSavedErrorToast); 1235548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell return null; 1236548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1237548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell try { 1238548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell if (c.getCount() < 2) { 1239d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell Log.e(TAG, "Not enough raw contacts to aggregate together."); 1240548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell return null; 1241548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1242548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell rawContactIds = new long[c.getCount()]; 1243548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell for (int i = 0; i < rawContactIds.length; i++) { 1244548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell c.moveToPosition(i); 1245548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell long rawContactId = c.getLong(JoinContactQuery._ID); 1246548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell rawContactIds[i] = rawContactId; 1247548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1248548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } finally { 1249548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell c.close(); 1250548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1251548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell return rawContactIds; 1252548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell } 1253548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell 1254d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell private long[] getRawContactIdsForAggregation(long contactId1, long contactId2) { 1255d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell return getRawContactIdsForAggregation(new long[] {contactId1, contactId2}); 1256d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell } 1257d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell 12582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov /** 12592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation. 12602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov */ 12612b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations, 12622b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long rawContactId1, long rawContactId2) { 12632b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Builder builder = 12642b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI); 12652b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER); 12662b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1); 12672b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2); 12682b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov operations.add(builder.build()); 12692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 1270886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov 1271886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov /** 1272886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov * Shows a toast on the UI thread. 1273886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov */ 1274886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov private void showToast(final int message) { 12753a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler.post(new Runnable() { 1276886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov 1277886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov @Override 1278886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov public void run() { 1279886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov Toast.makeText(ContactSaveService.this, message, Toast.LENGTH_LONG).show(); 1280886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov } 1281886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov }); 1282886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov } 12833a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 12843a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov private void deliverCallback(final Intent callbackIntent) { 12853a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler.post(new Runnable() { 12863a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 12873a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov @Override 12883a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public void run() { 12893a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallbackOnUiThread(callbackIntent); 12903a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 12913a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov }); 12923a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 12933a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 12943a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov void deliverCallbackOnUiThread(final Intent callbackIntent) { 12953a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // TODO: this assumes that if there are multiple instances of the same 12963a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // activity registered, the last one registered is the one waiting for 12973a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // the callback. Validity of this assumption needs to be verified. 1298a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson for (Listener listener : sListeners) { 1299a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson if (callbackIntent.getComponent().equals( 1300a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson ((Activity) listener).getIntent().getComponent())) { 1301a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson listener.onServiceCompleted(callbackIntent); 1302a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson return; 13033a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 13043a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 13053a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1306173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann} 1307