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