ContactSaveService.java revision 683b57e1fbf27c81c9973de814fc8bb1852e6df8
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 19a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport com.android.contacts.model.AccountTypeManager; 20a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport com.android.contacts.model.EntityDeltaList; 21a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport com.android.contacts.model.EntityModifier; 221ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikovimport com.google.android.collect.Lists; 23caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport com.google.android.collect.Sets; 24caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 25caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.accounts.Account; 263a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikovimport android.app.Activity; 27173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.app.IntentService; 28173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.content.ContentProviderOperation; 292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.content.ContentProviderOperation.Builder; 30caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentProviderResult; 31caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentResolver; 32e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikovimport android.content.ContentUris; 33caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentValues; 349d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikovimport android.content.Context; 35173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.content.Intent; 362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.content.OperationApplicationException; 372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.database.Cursor; 38caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.net.Uri; 39886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikovimport android.os.Handler; 40886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikovimport android.os.Looper; 41a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport android.os.Parcelable; 422b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.os.RemoteException; 43173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.provider.ContactsContract; 442b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.provider.ContactsContract.AggregationExceptions; 451ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.GroupMembership; 469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikovimport android.provider.ContactsContract.Contacts; 47caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.Data; 48e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikovimport android.provider.ContactsContract.Groups; 49caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.RawContacts; 50173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.util.Log; 512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.widget.Toast; 52173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 53173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport java.util.ArrayList; 54caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport java.util.HashSet; 553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikovimport java.util.LinkedList; 56caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport java.util.List; 57173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 5818ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov/** 5918ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov * A service responsible for saving changes to the content provider. 6018ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov */ 61173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannpublic class ContactSaveService extends IntentService { 62173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann private static final String TAG = "ContactSaveService"; 63173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 64a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan /** Set to true in order to view logs on content provider operations */ 65a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan private static final boolean DEBUG = false; 66a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan 67caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String ACTION_NEW_RAW_CONTACT = "newRawContact"; 68caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 69caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_ACCOUNT_NAME = "accountName"; 70caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_ACCOUNT_TYPE = "accountType"; 71caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_CONTENT_VALUES = "contentValues"; 72caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov public static final String EXTRA_CALLBACK_INTENT = "callbackIntent"; 73caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 74a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String ACTION_SAVE_CONTACT = "saveContact"; 75a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String EXTRA_CONTACT_STATE = "state"; 76a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static final String EXTRA_SAVE_MODE = "saveMode"; 77173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 781ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov public static final String ACTION_CREATE_GROUP = "createGroup"; 79e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String ACTION_RENAME_GROUP = "renameGroup"; 80e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String ACTION_DELETE_GROUP = "deleteGroup"; 812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String ACTION_UPDATE_GROUP = "updateGroup"; 82e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String EXTRA_GROUP_ID = "groupId"; 83e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov public static final String EXTRA_GROUP_LABEL = "groupLabel"; 842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String EXTRA_RAW_CONTACTS_TO_ADD = "rawContactsToAdd"; 852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static final String EXTRA_RAW_CONTACTS_TO_REMOVE = "rawContactsToRemove"; 86e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 879d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String ACTION_SET_STARRED = "setStarred"; 887d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov public static final String ACTION_DELETE_CONTACT = "delete"; 899d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String EXTRA_CONTACT_URI = "contactUri"; 909d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static final String EXTRA_STARRED_FLAG = "starred"; 919d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 920f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary"; 930f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String ACTION_CLEAR_PRIMARY = "clearPrimary"; 940f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static final String EXTRA_DATA_ID = "dataId"; 950f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String ACTION_JOIN_CONTACTS = "joinContacts"; 972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String EXTRA_CONTACT_ID1 = "contactId1"; 982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String EXTRA_CONTACT_ID2 = "contactId2"; 992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static final String EXTRA_CONTACT_WRITABLE = "contactWritable"; 1002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 101683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String ACTION_SET_SEND_TO_VOICEMAIL = "sendToVoicemail"; 102683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String EXTRA_SEND_TO_VOICEMAIL_FLAG = "sendToVoicemailFlag"; 103683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 104683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String ACTION_SET_RINGTONE = "setRingtone"; 105683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static final String EXTRA_CUSTOM_RINGTONE = "customRingtone"; 106683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 107caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet( 108caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.MIMETYPE, 109caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.IS_PRIMARY, 110caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA1, 111caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA2, 112caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA3, 113caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA4, 114caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA5, 115caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA6, 116caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA7, 117caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA8, 118caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA9, 119caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA10, 120caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA11, 121caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA12, 122caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA13, 123caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA14, 124caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Data.DATA15 125caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ); 126caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 127a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov private static final int PERSIST_TRIES = 3; 128a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 1293a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public interface Listener { 1303a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public void onServiceCompleted(Intent callbackIntent); 1313a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1323a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1333a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov private static final LinkedList<Listener> sListeners = new LinkedList<Listener>(); 1343a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1353a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov private Handler mMainHandler; 1363a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 137173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann public ContactSaveService() { 138173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann super(TAG); 139173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann setIntentRedelivery(true); 1403a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler = new Handler(Looper.getMainLooper()); 1413a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1423a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1433a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public static void registerListener(Listener listener) { 1443a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov if (!(listener instanceof Activity)) { 1453a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov throw new ClassCastException("Only activities can be registered to" 1463a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov + " receive callback from " + ContactSaveService.class.getName()); 1473a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1483a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov synchronized (sListeners) { 1493a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov sListeners.addFirst(listener); 1503a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1513a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 1523a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 1533a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public static void unregisterListener(Listener listener) { 1543a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov synchronized (sListeners) { 1553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov sListeners.remove(listener); 1563a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 157173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann } 158173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann 159173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann @Override 160a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public Object getSystemService(String name) { 161a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Object service = super.getSystemService(name); 162a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (service != null) { 163a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return service; 164a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 165a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 166a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return getApplicationContext().getSystemService(name); 167a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 168a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 169a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov @Override 170173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann protected void onHandleIntent(Intent intent) { 171caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String action = intent.getAction(); 172caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov if (ACTION_NEW_RAW_CONTACT.equals(action)) { 173caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov createRawContact(intent); 174a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } else if (ACTION_SAVE_CONTACT.equals(action)) { 175a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov saveContact(intent); 1761ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } else if (ACTION_CREATE_GROUP.equals(action)) { 1771ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov createGroup(intent); 178e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } else if (ACTION_RENAME_GROUP.equals(action)) { 179e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov renameGroup(intent); 180e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } else if (ACTION_DELETE_GROUP.equals(action)) { 181e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov deleteGroup(intent); 1822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } else if (ACTION_UPDATE_GROUP.equals(action)) { 1832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan updateGroup(intent); 1849d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } else if (ACTION_SET_STARRED.equals(action)) { 1859d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov setStarred(intent); 1860f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) { 1870f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann setSuperPrimary(intent); 1880f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } else if (ACTION_CLEAR_PRIMARY.equals(action)) { 1890f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann clearPrimary(intent); 1907d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } else if (ACTION_DELETE_CONTACT.equals(action)) { 1917d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov deleteContact(intent); 1922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } else if (ACTION_JOIN_CONTACTS.equals(action)) { 1932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov joinContacts(intent); 194683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) { 195683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson setSendToVoicemail(intent); 196683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } else if (ACTION_SET_RINGTONE.equals(action)) { 197683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson setRingtone(intent); 198caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 199caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 200caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 2019d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov /** 2029d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to create a new raw contact 2039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * using data presented as a set of ContentValues. 2049d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov */ 2059d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createNewRawContactIntent(Context context, 2069d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov ArrayList<ContentValues> values, Account account, Class<?> callbackActivity, 2079d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov String callbackAction) { 2089d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent( 2099d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov context, ContactSaveService.class); 2109d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT); 2119d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov if (account != null) { 2129d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name); 2139d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type); 2149d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 2159d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putParcelableArrayListExtra( 2169d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov ContactSaveService.EXTRA_CONTENT_VALUES, values); 2179d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 2189d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // Callback intent will be invoked by the service once the new contact is 2199d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // created. The service will put the URI of the new contact as "data" on 2209d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // the callback intent. 2219d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 2229d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov callbackIntent.setAction(callbackAction); 2239d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 2249d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov return serviceIntent; 2259d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 2269d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 227caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov private void createRawContact(Intent intent) { 228caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME); 229caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE); 230caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES); 231caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 232caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 233caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); 234caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) 235caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValue(RawContacts.ACCOUNT_NAME, accountName) 236caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValue(RawContacts.ACCOUNT_TYPE, accountType) 237caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .build()); 238caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 239caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov int size = valueList.size(); 240caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov for (int i = 0; i < size; i++) { 241caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentValues values = valueList.get(i); 242caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov values.keySet().retainAll(ALLOWED_DATA_COLUMNS); 243caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) 244caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValueBackReference(Data.RAW_CONTACT_ID, 0) 245caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .withValues(values) 246caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov .build()); 247caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 248caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 249caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentResolver resolver = getContentResolver(); 250caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov ContentProviderResult[] results; 251caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov try { 252caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov results = resolver.applyBatch(ContactsContract.AUTHORITY, operations); 253caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } catch (Exception e) { 254caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov throw new RuntimeException("Failed to store new contact", e); 255caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 256caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 257caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov Uri rawContactUri = results[0].uri; 258caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri)); 259caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 2603a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 261caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 262caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 263caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov /** 264a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov * Creates an intent that can be sent to this service to create a new raw contact 265a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov * using data presented as a set of ContentValues. 266a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov */ 267a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov public static Intent createSaveContactIntent(Context context, EntityDeltaList state, 268a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov String saveModeExtraKey, int saveMode, Class<?> callbackActivity, 269a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov String callbackAction) { 270a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Intent serviceIntent = new Intent( 271a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov context, ContactSaveService.class); 272a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT); 273a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state); 274a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 275a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Callback intent will be invoked by the service once the contact is 276a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // saved. The service will put the URI of the new contact as "data" on 277a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // the callback intent. 278a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 279a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov callbackIntent.putExtra(saveModeExtraKey, saveMode); 280a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov callbackIntent.setAction(callbackAction); 281a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 282a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return serviceIntent; 283a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 284a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 285a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov private void saveContact(Intent intent) { 286a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov EntityDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE); 287a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 288a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 289a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Trim any empty fields, and RawContacts, before persisting 290a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this); 291a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov EntityModifier.trimEmpty(state, accountTypes); 292a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 293a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Uri lookupUri = null; 294a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 295a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ContentResolver resolver = getContentResolver(); 296a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 297a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Attempt to persist changes 298a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov int tries = 0; 299a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov while (tries++ < PERSIST_TRIES) { 300a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov try { 301a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Build operations and try applying 302a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ArrayList<ContentProviderOperation> diff = state.buildDiff(); 303a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan if (DEBUG) { 304a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan Log.v(TAG, "Content Provider Operations:"); 305a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan for (ContentProviderOperation operation : diff) { 306a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan Log.v(TAG, operation.toString()); 307a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan } 308a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan } 309a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan 310a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov ContentProviderResult[] results = null; 311a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (!diff.isEmpty()) { 312a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov results = resolver.applyBatch(ContactsContract.AUTHORITY, diff); 313a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 314a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 315a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final long rawContactId = getRawContactId(state, diff, results); 316a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (rawContactId == -1) { 317a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov throw new IllegalStateException("Could not determine RawContact ID after save"); 318a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 319a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final Uri rawContactUri = ContentUris.withAppendedId( 320a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov RawContacts.CONTENT_URI, rawContactId); 321a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri); 322a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Log.v(TAG, "Saved contact. New URI: " + lookupUri); 323a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov break; 324a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 325a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } catch (RemoteException e) { 326a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Something went wrong, bail without success 327a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Log.e(TAG, "Problem persisting user edits", e); 328a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov break; 329a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 330a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } catch (OperationApplicationException e) { 331a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov // Version consistency failed, re-parent change and try again 332a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString()); 333a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN("); 334a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov boolean first = true; 335a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final int count = state.size(); 336a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov for (int i = 0; i < count; i++) { 337a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov Long rawContactId = state.getRawContactId(i); 338a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (rawContactId != null && rawContactId != -1) { 339a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (!first) { 340a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(','); 341a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 342a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(rawContactId); 343a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov first = false; 344a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 345a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 346a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.append(")"); 347a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 348a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (first) { 349a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov throw new IllegalStateException("Version consistency failed for a new contact"); 350a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 351a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 352a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final EntityDeltaList newState = EntityDeltaList.fromQuery(resolver, 353a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov sb.toString(), null, null); 354a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov state = EntityDeltaList.mergeAfter(newState, state); 355a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 356a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 357a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 358a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov callbackIntent.setData(lookupUri); 359a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 3603a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 361a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 362a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 363a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov private long getRawContactId(EntityDeltaList state, 364a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ArrayList<ContentProviderOperation> diff, 365a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final ContentProviderResult[] results) { 366a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov long rawContactId = state.findRawContactId(); 367a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (rawContactId != -1) { 368a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return rawContactId; 369a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 370a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 371a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov final int diffSize = diff.size(); 372a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov for (int i = 0; i < diffSize; i++) { 373a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov ContentProviderOperation operation = diff.get(i); 374a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov if (operation.getType() == ContentProviderOperation.TYPE_INSERT 375a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov && operation.getUri().getEncodedPath().contains( 376a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov RawContacts.CONTENT_URI.getEncodedPath())) { 377a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return ContentUris.parseId(results[i].uri); 378a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 379a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 380a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov return -1; 381a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov } 382a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov 383a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov /** 384717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * Creates an intent that can be sent to this service to create a new group as 385717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * well as add new members at the same time. 386717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * 387717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param context of the application 388717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param account in which the group should be created 389717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param label is the name of the group (cannot be null) 390717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param rawContactsToAdd is an array of raw contact IDs for contacts that 391717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * should be added to the group 392717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param callbackActivity is the activity to send the callback intent to 393717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan * @param callbackAction is the intent action for the callback intent 394caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov */ 395683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 396717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan public static Intent createNewGroupIntent(Context context, Account account, 397717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan String label, long[] rawContactsToAdd, Class<?> callbackActivity, 398717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan String callbackAction) { 3999d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 4009d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP); 4019d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type); 4029d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name); 4039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label); 404717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd); 405caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov 4069d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov // Callback intent will be invoked by the service once the new group is 407717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // created. 4089d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 4099d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov callbackIntent.setAction(callbackAction); 4101ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 4119d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 4121ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov return serviceIntent; 4131ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 4141ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 4151ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov private void createGroup(Intent intent) { 416717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE); 417717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME); 418717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 419717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD); 4201ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 4211ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov ContentValues values = new ContentValues(); 4221ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.ACCOUNT_TYPE, accountType); 4231ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.ACCOUNT_NAME, accountName); 4241ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Groups.TITLE, label); 4251ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 426717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final ContentResolver resolver = getContentResolver(); 427717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 428717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Create the new group 429717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values); 430717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 431717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // If there's no URI, then the insertion failed. Abort early because group members can't be 432717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // added if the group doesn't exist 4331ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov if (groupUri == null) { 434717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan Log.e(TAG, "Couldn't create group with label " + label); 4351ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov return; 4361ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 4371ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 438717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Add new group members 439717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri)); 440717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 441717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // TODO: Move this into the contact editor where it belongs. This needs to be integrated 442717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // with the way other intent extras that are passed to the {@link ContactEditorActivity}. 4431ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.clear(); 4441ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 4451ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri)); 4461ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 4471ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 448c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setData(groupUri); 449717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // TODO: This can be taken out when the above TODO is addressed 4501ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values)); 4513a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 4521ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov } 4531ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov 4541ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov /** 4559d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to rename a group. 4561ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov */ 457c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel, 458c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan Class<?> callbackActivity, String callbackAction) { 4599d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 4609d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP); 4619d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 4629d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel); 463c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 464c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan // Callback intent will be invoked by the service once the group is renamed. 465c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan Intent callbackIntent = new Intent(context, callbackActivity); 466c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setAction(callbackAction); 467c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 468c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 469caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov return serviceIntent; 470caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov } 471e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 472e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov private void renameGroup(Intent intent) { 473e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 474e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 475e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 476e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov if (groupId == -1) { 477e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov Log.e(TAG, "Invalid arguments for renameGroup request"); 478e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return; 479e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 480e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 481e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov ContentValues values = new ContentValues(); 482e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov values.put(Groups.TITLE, label); 483c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId); 484c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan getContentResolver().update(groupUri, values, null, null); 485c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan 486c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 487c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan callbackIntent.setData(groupUri); 488c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan deliverCallback(callbackIntent); 489e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 490e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 491e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov /** 4929d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to delete a group. 493e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov */ 4949d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createGroupDeletionIntent(Context context, long groupId) { 4959d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 4969d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP); 497e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 498e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return serviceIntent; 499e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 500e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 501e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov private void deleteGroup(Intent intent) { 502e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 503e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov if (groupId == -1) { 504e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov Log.e(TAG, "Invalid arguments for deleteGroup request"); 505e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return; 506e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 507e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 508e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov getContentResolver().delete( 509e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null); 510e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 511e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov 512e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov /** 5132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * Creates an intent that can be sent to this service to rename a group as 5142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * well as add and remove members from the group. 5152d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * 5162d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param context of the application 5172d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param groupId of the group that should be modified 5182d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param newLabel is the updated name of the group (can be null if the name 5192d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * should not be updated) 5202d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param rawContactsToAdd is an array of raw contact IDs for contacts that 5212d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * should be added to the group 5222d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param rawContactsToRemove is an array of raw contact IDs for contacts 5232d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * that should be removed from the group 5242d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param callbackActivity is the activity to send the callback intent to 5252d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan * @param callbackAction is the intent action for the callback intent 5262d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan */ 5272d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel, 5282d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToAdd, long[] rawContactsToRemove, 5292d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Class<?> callbackActivity, String callbackAction) { 5302d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Intent serviceIntent = new Intent(context, ContactSaveService.class); 5312d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP); 5322d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); 5332d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel); 5342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd); 5352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_REMOVE, 5362d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactsToRemove); 5372d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 5382d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Callback intent will be invoked by the service once the group is updated 5392d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Intent callbackIntent = new Intent(context, callbackActivity); 5402d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan callbackIntent.setAction(callbackAction); 5412d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 5422d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 5432d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan return serviceIntent; 5442d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 5452d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 5462d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan private void updateGroup(Intent intent) { 5472d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); 5482d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String label = intent.getStringExtra(EXTRA_GROUP_LABEL); 5492d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD); 5502d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan long[] rawContactsToRemove = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_REMOVE); 5512d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 5522d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (groupId == -1) { 5532d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.e(TAG, "Invalid arguments for updateGroup request"); 5542d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan return; 5552d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 5562d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 5572d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentResolver resolver = getContentResolver(); 5582d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId); 5592d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 5602d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Update group name if necessary 5612d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (label != null) { 5622d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan ContentValues values = new ContentValues(); 5632d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan values.put(Groups.TITLE, label); 564717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan resolver.update(groupUri, values, null, null); 5652d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 5662d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 567717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan // Add and remove members if necessary 568717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan addMembersToGroup(resolver, rawContactsToAdd, groupId); 569717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan removeMembersFromGroup(resolver, rawContactsToRemove, groupId); 570717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 571717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 572717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan callbackIntent.setData(groupUri); 573717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan deliverCallback(callbackIntent); 574717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 575717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan 576717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan private void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd, 577717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan long groupId) { 578717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan if (rawContactsToAdd == null) { 579717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan return; 580717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 5812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (long rawContactId : rawContactsToAdd) { 5822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan try { 5832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ArrayList<ContentProviderOperation> rawContactOperations = 5842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new ArrayList<ContentProviderOperation>(); 5852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 5862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Build an assert operation to ensure the contact is not already in the group 5872d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentProviderOperation.Builder assertBuilder = ContentProviderOperation 5882d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan .newAssertQuery(Data.CONTENT_URI); 5892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan assertBuilder.withSelection(Data.RAW_CONTACT_ID + "=? AND " + 5902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?", 5912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new String[] { String.valueOf(rawContactId), 5922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)}); 5932d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan assertBuilder.withExpectedCount(0); 5942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactOperations.add(assertBuilder.build()); 5952d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 5962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Build an insert operation to add the contact to the group 5972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan final ContentProviderOperation.Builder insertBuilder = ContentProviderOperation 5982d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan .newInsert(Data.CONTENT_URI); 5992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(Data.RAW_CONTACT_ID, rawContactId); 6002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); 6012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan insertBuilder.withValue(GroupMembership.GROUP_ROW_ID, groupId); 6022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan rawContactOperations.add(insertBuilder.build()); 6032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 6042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (DEBUG) { 6052d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (ContentProviderOperation operation : rawContactOperations) { 6062d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.v(TAG, operation.toString()); 6072d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 6082d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 6092d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 6102d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Apply batch 6112d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan ContentProviderResult[] results = null; 6122d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan if (!rawContactOperations.isEmpty()) { 6132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan results = resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations); 6142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 6152d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } catch (RemoteException e) { 6162d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Something went wrong, bail without success 6172d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.e(TAG, "Problem persisting user edits for raw contact ID " + 6182d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(rawContactId), e); 6192d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } catch (OperationApplicationException e) { 6202d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // The assert could have failed because the contact is already in the group, 6212d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // just continue to the next contact 6222d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Log.w(TAG, "Assert failed in adding raw contact ID " + 6232d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(rawContactId) + ". Already exists in group " + 6242d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan String.valueOf(groupId), e); 6252d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 6262d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 627717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 6282d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 629717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan private void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove, 630717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan long groupId) { 631717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan if (rawContactsToRemove == null) { 632717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan return; 633717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan } 6342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan for (long rawContactId : rawContactsToRemove) { 6352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // Apply the delete operation on the data row for the given raw contact's 6362d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // membership in the given group. If no contact matches the provided selection, then 6372d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan // nothing will be done. Just continue to the next contact. 6382d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan getContentResolver().delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " + 6392d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?", 6402d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan new String[] { String.valueOf(rawContactId), 6412d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)}); 6422d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 6432d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan } 6442d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan 6452d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan /** 6469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov * Creates an intent that can be sent to this service to star or un-star a contact. 647e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov */ 6489d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) { 6499d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 6509d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED); 6519d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 6529d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value); 6539d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 654e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov return serviceIntent; 655e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov } 6569d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 6579d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov private void setStarred(Intent intent) { 6589d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 6599d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false); 6609d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov if (contactUri == null) { 6619d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov Log.e(TAG, "Invalid arguments for setStarred request"); 6629d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov return; 6639d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 6649d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov 6659d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov final ContentValues values = new ContentValues(1); 6669d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov values.put(Contacts.STARRED, value); 6679d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov getContentResolver().update(contactUri, values, null, null); 6689d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov } 6690f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 6700f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann /** 671683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson * Creates an intent that can be sent to this service to set the redirect to voicemail. 672683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson */ 673683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static Intent createSetSendToVoicemail(Context context, Uri contactUri, 674683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson boolean value) { 675683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Intent serviceIntent = new Intent(context, ContactSaveService.class); 676683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.setAction(ContactSaveService.ACTION_SET_SEND_TO_VOICEMAIL); 677683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 678683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_SEND_TO_VOICEMAIL_FLAG, value); 679683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 680683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return serviceIntent; 681683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 682683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 683683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson private void setSendToVoicemail(Intent intent) { 684683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 685683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson boolean value = intent.getBooleanExtra(EXTRA_SEND_TO_VOICEMAIL_FLAG, false); 686683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson if (contactUri == null) { 687683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Log.e(TAG, "Invalid arguments for setRedirectToVoicemail"); 688683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return; 689683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 690683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 691683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson final ContentValues values = new ContentValues(1); 692683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson values.put(Contacts.SEND_TO_VOICEMAIL, value); 693683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson getContentResolver().update(contactUri, values, null, null); 694683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 695683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 696683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson /** 697683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson * Creates an intent that can be sent to this service to save the contact's ringtone. 698683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson */ 699683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson public static Intent createSetRingtone(Context context, Uri contactUri, 700683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson String value) { 701683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Intent serviceIntent = new Intent(context, ContactSaveService.class); 702683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.setAction(ContactSaveService.ACTION_SET_RINGTONE); 703683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 704683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson serviceIntent.putExtra(ContactSaveService.EXTRA_CUSTOM_RINGTONE, value); 705683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 706683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return serviceIntent; 707683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 708683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 709683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson private void setRingtone(Intent intent) { 710683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 711683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson String value = intent.getStringExtra(EXTRA_CUSTOM_RINGTONE); 712683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson if (contactUri == null) { 713683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson Log.e(TAG, "Invalid arguments for setRingtone"); 714683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson return; 715683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 716683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson ContentValues values = new ContentValues(1); 717683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson values.put(Contacts.CUSTOM_RINGTONE, value); 718683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson getContentResolver().update(contactUri, values, null, null); 719683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson } 720683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson 721683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson /** 7220f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * Creates an intent that sets the selected data item as super primary (default) 7230f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann */ 7240f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static Intent createSetSuperPrimaryIntent(Context context, long dataId) { 7250f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Intent serviceIntent = new Intent(context, ContactSaveService.class); 7260f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY); 7270f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId); 7280f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return serviceIntent; 7290f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 7300f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 7310f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann private void setSuperPrimary(Intent intent) { 7320f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1); 7330f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann if (dataId == -1) { 7340f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Log.e(TAG, "Invalid arguments for setSuperPrimary request"); 7350f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return; 7360f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 7370f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 7380f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann // Update the primary values in the data record. 7390f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann ContentValues values = new ContentValues(1); 7400f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_SUPER_PRIMARY, 1); 7410f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_PRIMARY, 1); 7420f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 7430f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), 7440f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values, null, null); 7450f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 7460f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 7470f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann /** 7480f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * Creates an intent that clears the primary flag of all data items that belong to the same 7490f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * raw_contact as the given data item. Will only clear, if the data item was primary before 7500f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann * this call 7510f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann */ 7520f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann public static Intent createClearPrimaryIntent(Context context, long dataId) { 7530f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Intent serviceIntent = new Intent(context, ContactSaveService.class); 7540f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY); 7550f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId); 7560f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return serviceIntent; 7570f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 7580f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 7590f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann private void clearPrimary(Intent intent) { 7600f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1); 7610f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann if (dataId == -1) { 7620f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann Log.e(TAG, "Invalid arguments for clearPrimary request"); 7630f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann return; 7640f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 7650f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 7660f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann // Update the primary values in the data record. 7670f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann ContentValues values = new ContentValues(1); 7680f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_SUPER_PRIMARY, 0); 7690f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values.put(Data.IS_PRIMARY, 0); 7700f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann 7710f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), 7720f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann values, null, null); 7730f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann } 7747d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 7757d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov /** 7767d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov * Creates an intent that can be sent to this service to delete a contact. 7777d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov */ 7787d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov public static Intent createDeleteContactIntent(Context context, Uri contactUri) { 7797d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 7807d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT); 7817d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri); 7827d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov return serviceIntent; 7837d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 7847d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 7857d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov private void deleteContact(Intent intent) { 7867d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); 7877d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov if (contactUri == null) { 7887d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov Log.e(TAG, "Invalid arguments for deleteContact request"); 7897d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov return; 7907d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 7917d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov 7927d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov getContentResolver().delete(contactUri, null, null); 7937d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov } 7942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 7952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov /** 7962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov * Creates an intent that can be sent to this service to join two contacts. 7972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov */ 7982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov public static Intent createJoinContactsIntent(Context context, long contactId1, 7992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long contactId2, boolean contactWritable, 8002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Class<?> callbackActivity, String callbackAction) { 8012b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent serviceIntent = new Intent(context, ContactSaveService.class); 8022b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS); 8032b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1); 8042b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2); 8052b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_WRITABLE, contactWritable); 8062b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 8072b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Callback intent will be invoked by the service once the contacts are joined. 8082b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent callbackIntent = new Intent(context, callbackActivity); 8092b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov callbackIntent.setAction(callbackAction); 8102b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); 8112b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 8122b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov return serviceIntent; 8132b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 8142b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 8152b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 8162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov private interface JoinContactQuery { 8172b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov String[] PROJECTION = { 8182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts._ID, 8192b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts.CONTACT_ID, 8202b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts.NAME_VERIFIED, 8212b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov RawContacts.DISPLAY_NAME_SOURCE, 8222b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov }; 8232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 8242b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?"; 8252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 8262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int _ID = 0; 8272b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int CONTACT_ID = 1; 8282b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int NAME_VERIFIED = 2; 8292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int DISPLAY_NAME_SOURCE = 3; 8302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 8312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 8322b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov private void joinContacts(Intent intent) { 8332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1); 8342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1); 8352b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov boolean writable = intent.getBooleanExtra(EXTRA_CONTACT_WRITABLE, false); 8362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (contactId1 == -1 || contactId2 == -1) { 8372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Log.e(TAG, "Invalid arguments for joinContacts request"); 8382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov return; 8392b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 8402b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 8412b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov final ContentResolver resolver = getContentResolver(); 8422b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 8432b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Load raw contact IDs for all raw contacts involved - currently edited and selected 8442b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // in the join UIs 8452b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Cursor c = resolver.query(RawContacts.CONTENT_URI, 8462b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov JoinContactQuery.PROJECTION, 8472b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov JoinContactQuery.SELECTION, 8482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov new String[]{String.valueOf(contactId1), String.valueOf(contactId2)}, null); 8492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 8502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long rawContactIds[]; 8512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long verifiedNameRawContactId = -1; 8522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov try { 8532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int maxDisplayNameSource = -1; 8542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov rawContactIds = new long[c.getCount()]; 8552b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int i = 0; i < rawContactIds.length; i++) { 8562b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov c.moveToPosition(i); 8572b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long rawContactId = c.getLong(JoinContactQuery._ID); 8582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov rawContactIds[i] = rawContactId; 8592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE); 8602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (nameSource > maxDisplayNameSource) { 8612b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov maxDisplayNameSource = nameSource; 8622b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 8632b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 8642b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 8652b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Find an appropriate display name for the joined contact: 8662b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // if should have a higher DisplayNameSource or be the name 8672b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // of the original contact that we are joining with another. 8682b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (writable) { 8692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int i = 0; i < rawContactIds.length; i++) { 8702b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov c.moveToPosition(i); 8712b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (c.getLong(JoinContactQuery.CONTACT_ID) == contactId1) { 8722b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE); 8732b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (nameSource == maxDisplayNameSource 8742b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov && (verifiedNameRawContactId == -1 8752b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0)) { 8762b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov verifiedNameRawContactId = c.getLong(JoinContactQuery._ID); 8772b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 8782b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 8792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 8802b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 8812b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } finally { 8822b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov c.close(); 8832b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 8842b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 8852b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // For each pair of raw contacts, insert an aggregation exception 8862b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); 8872b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int i = 0; i < rawContactIds.length; i++) { 8882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov for (int j = 0; j < rawContactIds.length; j++) { 8892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (i != j) { 8902b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]); 8912b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 8922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 8932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 8942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 8952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Mark the original contact as "name verified" to make sure that the contact 8962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // display name does not change as a result of the join 8972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (verifiedNameRawContactId != -1) { 8982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Builder builder = ContentProviderOperation.newUpdate( 8992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId)); 9002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(RawContacts.NAME_VERIFIED, 1); 9012b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov operations.add(builder.build()); 9022b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 9032b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9042b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov boolean success = false; 9052b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov // Apply all aggregation exceptions as one batch 9062b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov try { 9072b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov resolver.applyBatch(ContactsContract.AUTHORITY, operations); 908886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov showToast(R.string.contactsJoinedMessage); 9092b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov success = true; 9102b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } catch (RemoteException e) { 9112b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Log.e(TAG, "Failed to apply aggregation exception batch", e); 912886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov showToast(R.string.contactSavedErrorToast); 9132b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } catch (OperationApplicationException e) { 9142b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Log.e(TAG, "Failed to apply aggregation exception batch", e); 915886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov showToast(R.string.contactSavedErrorToast); 9162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 9172b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); 9192b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov if (success) { 9202b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Uri uri = RawContacts.getContactLookupUri(resolver, 9212b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0])); 9222b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov callbackIntent.setData(uri); 9232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 9243a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallback(callbackIntent); 9252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 9262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov 9272b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov /** 9282b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation. 9292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov */ 9302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations, 9312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov long rawContactId1, long rawContactId2) { 9322b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov Builder builder = 9332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI); 9342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER); 9352b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1); 9362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2); 9372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov operations.add(builder.build()); 9382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov } 939886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov 940886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov /** 941886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov * Shows a toast on the UI thread. 942886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov */ 943886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov private void showToast(final int message) { 9443a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler.post(new Runnable() { 945886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov 946886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov @Override 947886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov public void run() { 948886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov Toast.makeText(ContactSaveService.this, message, Toast.LENGTH_LONG).show(); 949886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov } 950886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov }); 951886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov } 9523a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 9533a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov private void deliverCallback(final Intent callbackIntent) { 9543a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov mMainHandler.post(new Runnable() { 9553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 9563a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov @Override 9573a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov public void run() { 9583a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov deliverCallbackOnUiThread(callbackIntent); 9593a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 9603a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov }); 9613a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 9623a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov 9633a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov void deliverCallbackOnUiThread(final Intent callbackIntent) { 9643a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // TODO: this assumes that if there are multiple instances of the same 9653a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // activity registered, the last one registered is the one waiting for 9663a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov // the callback. Validity of this assumption needs to be verified. 9673a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov synchronized (sListeners) { 9683a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov for (Listener listener : sListeners) { 9693a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov if (callbackIntent.getComponent().equals( 9703a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov ((Activity) listener).getIntent().getComponent())) { 9713a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov listener.onServiceCompleted(callbackIntent); 9723a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov return; 9733a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 9743a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 9753a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 9763a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov } 977173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann} 978