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