ContactSaveService.java revision 637a38ec9de6b1f434d7a13105f2e747faae5107
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;
43e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Leeimport android.provider.ContactsContract.PinnedPositions;
44ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelsonimport android.provider.ContactsContract.Profile;
45caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.RawContacts;
46c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoroimport android.provider.ContactsContract.RawContactsEntity;
47173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.util.Log;
482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.widget.Toast;
49173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
50d7ca03e23948c3b2d5f97ec5598d8b50e3fc0b25Chiao Chengimport com.android.contacts.common.database.ContactUpdateUtils;
510d5588da244d0992c3ff8f25d0875fdf95a8c644Chiao Chengimport com.android.contacts.common.model.AccountTypeManager;
52851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.RawContactDelta;
53851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.RawContactDeltaList;
5447b6f70eadb118d815b4aaf5426c070bd75a38fbChiao Chengimport com.android.contacts.model.RawContactModifier;
55428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Chengimport com.android.contacts.common.model.account.AccountWithDataSet;
56e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.contacts.util.CallerInfoCacheUtils;
57637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport com.android.contacts.util.ContactPhotoUtils;
58637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee
59e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Lists;
60e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Sets;
61e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Cheng
62e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargusimport java.io.File;
63e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargusimport java.io.FileInputStream;
64e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargusimport java.io.FileOutputStream;
65e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargusimport java.io.IOException;
66637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport java.io.InputStream;
67c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.ArrayList;
68c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.HashSet;
69c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.List;
70c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.concurrent.CopyOnWriteArrayList;
71173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
7218ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov/**
7318ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov * A service responsible for saving changes to the content provider.
7418ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov */
75173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannpublic class ContactSaveService extends IntentService {
76173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    private static final String TAG = "ContactSaveService";
77173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
78a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan    /** Set to true in order to view logs on content provider operations */
79a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan    private static final boolean DEBUG = false;
80a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan
81caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String ACTION_NEW_RAW_CONTACT = "newRawContact";
82caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
83caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_ACCOUNT_NAME = "accountName";
84caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_ACCOUNT_TYPE = "accountType";
852b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    public static final String EXTRA_DATA_SET = "dataSet";
86caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_CONTENT_VALUES = "contentValues";
87caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_CALLBACK_INTENT = "callbackIntent";
88caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
89a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String ACTION_SAVE_CONTACT = "saveContact";
90a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String EXTRA_CONTACT_STATE = "state";
91a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String EXTRA_SAVE_MODE = "saveMode";
92ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson    public static final String EXTRA_SAVE_IS_PROFILE = "saveIsProfile";
9336d24d7ede42a252c82c4aa783b2231c5e2eea79Dave Santoro    public static final String EXTRA_SAVE_SUCCEEDED = "saveSucceeded";
94e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    public static final String EXTRA_UPDATED_PHOTOS = "updatedPhotos";
95173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
961ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    public static final String ACTION_CREATE_GROUP = "createGroup";
97e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String ACTION_RENAME_GROUP = "renameGroup";
98e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String ACTION_DELETE_GROUP = "deleteGroup";
992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String ACTION_UPDATE_GROUP = "updateGroup";
100e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String EXTRA_GROUP_ID = "groupId";
101e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String EXTRA_GROUP_LABEL = "groupLabel";
1022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String EXTRA_RAW_CONTACTS_TO_ADD = "rawContactsToAdd";
1032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String EXTRA_RAW_CONTACTS_TO_REMOVE = "rawContactsToRemove";
104e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
1059d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String ACTION_SET_STARRED = "setStarred";
1067d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    public static final String ACTION_DELETE_CONTACT = "delete";
1079d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String EXTRA_CONTACT_URI = "contactUri";
1089d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String EXTRA_STARRED_FLAG = "starred";
1099d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
1100f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary";
1110f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String ACTION_CLEAR_PRIMARY = "clearPrimary";
1120f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String EXTRA_DATA_ID = "dataId";
1130f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
1142b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String ACTION_JOIN_CONTACTS = "joinContacts";
1152b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String EXTRA_CONTACT_ID1 = "contactId1";
1162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String EXTRA_CONTACT_ID2 = "contactId2";
1172b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String EXTRA_CONTACT_WRITABLE = "contactWritable";
1182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
119683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String ACTION_SET_SEND_TO_VOICEMAIL = "sendToVoicemail";
120683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String EXTRA_SEND_TO_VOICEMAIL_FLAG = "sendToVoicemailFlag";
121683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
122683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String ACTION_SET_RINGTONE = "setRingtone";
123683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String EXTRA_CUSTOM_RINGTONE = "customRingtone";
124683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
125caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet(
126caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.MIMETYPE,
127caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.IS_PRIMARY,
128caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA1,
129caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA2,
130caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA3,
131caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA4,
132caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA5,
133caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA6,
134caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA7,
135caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA8,
136caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA9,
137caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA10,
138caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA11,
139caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA12,
140caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA13,
141caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA14,
142caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA15
143caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    );
144caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
145a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    private static final int PERSIST_TRIES = 3;
146a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
1473a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public interface Listener {
1483a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        public void onServiceCompleted(Intent callbackIntent);
1493a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1503a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
151a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson    private static final CopyOnWriteArrayList<Listener> sListeners =
152a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson            new CopyOnWriteArrayList<Listener>();
1533a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1543a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    private Handler mMainHandler;
1553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
156173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    public ContactSaveService() {
157173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann        super(TAG);
158173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann        setIntentRedelivery(true);
1593a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler = new Handler(Looper.getMainLooper());
1603a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1613a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1623a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public static void registerListener(Listener listener) {
1633a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        if (!(listener instanceof Activity)) {
1643a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            throw new ClassCastException("Only activities can be registered to"
1653a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                    + " receive callback from " + ContactSaveService.class.getName());
1663a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        }
167a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson        sListeners.add(0, listener);
1683a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1693a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1703a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public static void unregisterListener(Listener listener) {
171a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson        sListeners.remove(listener);
172173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    }
173173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
174173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    @Override
175a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public Object getSystemService(String name) {
176a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Object service = super.getSystemService(name);
177a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        if (service != null) {
178a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            return service;
179a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
180a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
181a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return getApplicationContext().getSystemService(name);
182a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
183a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
184a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    @Override
185173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    protected void onHandleIntent(Intent intent) {
1862f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa        // Call an appropriate method. If we're sure it affects how incoming phone calls are
1872f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa        // handled, then notify the fact to in-call screen.
188caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String action = intent.getAction();
189caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        if (ACTION_NEW_RAW_CONTACT.equals(action)) {
190caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            createRawContact(intent);
1912f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa            CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this);
192a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        } else if (ACTION_SAVE_CONTACT.equals(action)) {
193a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            saveContact(intent);
1942f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa            CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this);
1951ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        } else if (ACTION_CREATE_GROUP.equals(action)) {
1961ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov            createGroup(intent);
197e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        } else if (ACTION_RENAME_GROUP.equals(action)) {
198e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            renameGroup(intent);
199e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        } else if (ACTION_DELETE_GROUP.equals(action)) {
200e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            deleteGroup(intent);
2012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        } else if (ACTION_UPDATE_GROUP.equals(action)) {
2022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            updateGroup(intent);
2039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        } else if (ACTION_SET_STARRED.equals(action)) {
2049d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            setStarred(intent);
2050f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) {
2060f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            setSuperPrimary(intent);
2070f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        } else if (ACTION_CLEAR_PRIMARY.equals(action)) {
2080f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            clearPrimary(intent);
2097d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        } else if (ACTION_DELETE_CONTACT.equals(action)) {
2107d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            deleteContact(intent);
2112f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa            CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this);
2122b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } else if (ACTION_JOIN_CONTACTS.equals(action)) {
2132b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            joinContacts(intent);
2142f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa            CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this);
215683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) {
216683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            setSendToVoicemail(intent);
2172f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa            CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this);
218683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        } else if (ACTION_SET_RINGTONE.equals(action)) {
219683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            setRingtone(intent);
2202f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa            CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this);
221caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
222caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
223caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
2249d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    /**
2259d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to create a new raw contact
2269d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * using data presented as a set of ContentValues.
2279d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     */
2289d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createNewRawContactIntent(Context context,
2292b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            ArrayList<ContentValues> values, AccountWithDataSet account,
230e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction) {
2319d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(
2329d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov                context, ContactSaveService.class);
2339d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT);
2349d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        if (account != null) {
2359d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
2369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
2372b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet);
2389d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        }
2399d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putParcelableArrayListExtra(
2409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov                ContactSaveService.EXTRA_CONTENT_VALUES, values);
2419d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
2429d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // Callback intent will be invoked by the service once the new contact is
2439d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // created.  The service will put the URI of the new contact as "data" on
2449d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // the callback intent.
2459d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
2469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        callbackIntent.setAction(callbackAction);
2479d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
2489d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        return serviceIntent;
2499d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    }
2509d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
251caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    private void createRawContact(Intent intent) {
252caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
253caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
2542b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String dataSet = intent.getStringExtra(EXTRA_DATA_SET);
255caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES);
256caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
257caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
258caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
259caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
260caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .withValue(RawContacts.ACCOUNT_NAME, accountName)
261caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .withValue(RawContacts.ACCOUNT_TYPE, accountType)
2622b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                .withValue(RawContacts.DATA_SET, dataSet)
263caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .build());
264caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
265caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        int size = valueList.size();
266caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        for (int i = 0; i < size; i++) {
267caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            ContentValues values = valueList.get(i);
268caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            values.keySet().retainAll(ALLOWED_DATA_COLUMNS);
269caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
270caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .withValueBackReference(Data.RAW_CONTACT_ID, 0)
271caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .withValues(values)
272caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .build());
273caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
274caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
275caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ContentResolver resolver = getContentResolver();
276caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ContentProviderResult[] results;
277caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        try {
278caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            results = resolver.applyBatch(ContactsContract.AUTHORITY, operations);
279caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        } catch (Exception e) {
280caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            throw new RuntimeException("Failed to store new contact", e);
281caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
282caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
283caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Uri rawContactUri = results[0].uri;
284caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri));
285caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
2863a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
287caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
288caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
289caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    /**
290a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     * Creates an intent that can be sent to this service to create a new raw contact
291a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     * using data presented as a set of ContentValues.
292e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * This variant is more convenient to use when there is only one photo that can
293e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * possibly be updated, as in the Contact Details screen.
294e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * @param rawContactId identifies a writable raw-contact whose photo is to be updated.
295e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * @param updatedPhotoPath denotes a temporary file containing the contact's new photo.
296a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     */
297851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu    public static Intent createSaveContactIntent(Context context, RawContactDeltaList state,
298e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            String saveModeExtraKey, int saveMode, boolean isProfile,
299e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction, long rawContactId,
300637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            Uri updatedPhotoPath) {
301e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        Bundle bundle = new Bundle();
302637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        bundle.putParcelable(String.valueOf(rawContactId), updatedPhotoPath);
303e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        return createSaveContactIntent(context, state, saveModeExtraKey, saveMode, isProfile,
304e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                callbackActivity, callbackAction, bundle);
305e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    }
306e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
307e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    /**
308e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * Creates an intent that can be sent to this service to create a new raw contact
309e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * using data presented as a set of ContentValues.
310e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * This variant is used when multiple contacts' photos may be updated, as in the
311e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * Contact Editor.
312e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * @param updatedPhotos maps each raw-contact's ID to the file-path of the new photo.
313e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     */
314851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu    public static Intent createSaveContactIntent(Context context, RawContactDeltaList state,
315e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            String saveModeExtraKey, int saveMode, boolean isProfile,
316e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction,
317e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Bundle updatedPhotos) {
318a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Intent serviceIntent = new Intent(
319a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                context, ContactSaveService.class);
320a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT);
321a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state);
322ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson        serviceIntent.putExtra(EXTRA_SAVE_IS_PROFILE, isProfile);
323e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        if (updatedPhotos != null) {
324e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus            serviceIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos);
325e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        }
326a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
327e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        if (callbackActivity != null) {
328e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            // Callback intent will be invoked by the service once the contact is
329e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            // saved.  The service will put the URI of the new contact as "data" on
330e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            // the callback intent.
331e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Intent callbackIntent = new Intent(context, callbackActivity);
332e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            callbackIntent.putExtra(saveModeExtraKey, saveMode);
333e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            callbackIntent.setAction(callbackAction);
334e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
335e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        }
336a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return serviceIntent;
337a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
338a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
339a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    private void saveContact(Intent intent) {
340851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu        RawContactDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE);
341ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson        boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false);
342e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        Bundle updatedPhotos = intent.getParcelableExtra(EXTRA_UPDATED_PHOTOS);
343a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
344a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // Trim any empty fields, and RawContacts, before persisting
345a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
346851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu        RawContactModifier.trimEmpty(state, accountTypes);
347a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
348a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Uri lookupUri = null;
349a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
350a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final ContentResolver resolver = getContentResolver();
351e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        boolean succeeded = false;
352a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
353ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        // Keep track of the id of a newly raw-contact (if any... there can be at most one).
354ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        long insertedRawContactId = -1;
355ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus
356a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // Attempt to persist changes
357a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        int tries = 0;
358a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        while (tries++ < PERSIST_TRIES) {
359a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            try {
360a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Build operations and try applying
361a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final ArrayList<ContentProviderOperation> diff = state.buildDiff();
362a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                if (DEBUG) {
363a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    Log.v(TAG, "Content Provider Operations:");
364a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    for (ContentProviderOperation operation : diff) {
365a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                        Log.v(TAG, operation.toString());
366a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    }
367a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                }
368a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan
369a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                ContentProviderResult[] results = null;
370a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (!diff.isEmpty()) {
371a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
372a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
373a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
374a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final long rawContactId = getRawContactId(state, diff, results);
375a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (rawContactId == -1) {
376a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    throw new IllegalStateException("Could not determine RawContact ID after save");
377a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
378ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                // We don't have to check to see if the value is still -1.  If we reach here,
379ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                // the previous loop iteration didn't succeed, so any ID that we obtained is bogus.
380ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                insertedRawContactId = getInsertedRawContactId(diff, results);
3817c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                if (isProfile) {
3827c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    // Since the profile supports local raw contacts, which may have been completely
3837c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    // removed if all information was removed, we need to do a special query to
3847c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    // get the lookup URI for the profile contact (if it still exists).
3857c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    Cursor c = resolver.query(Profile.CONTENT_URI,
3867c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                            new String[] {Contacts._ID, Contacts.LOOKUP_KEY},
3877c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                            null, null, null);
3887c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    try {
389162b7e34fb581d0fa279957af5136d190c40759fErik                        if (c.moveToFirst()) {
390162b7e34fb581d0fa279957af5136d190c40759fErik                            final long contactId = c.getLong(0);
391162b7e34fb581d0fa279957af5136d190c40759fErik                            final String lookupKey = c.getString(1);
392162b7e34fb581d0fa279957af5136d190c40759fErik                            lookupUri = Contacts.getLookupUri(contactId, lookupKey);
393162b7e34fb581d0fa279957af5136d190c40759fErik                        }
3947c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    } finally {
3957c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                        c.close();
3967c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    }
3977c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                } else {
3987c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
3997c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                                    rawContactId);
4007c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri);
4017c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                }
402a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                Log.v(TAG, "Saved contact. New URI: " + lookupUri);
403e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
404e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                // We can change this back to false later, if we fail to save the contact photo.
405e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                succeeded = true;
406a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                break;
407a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
408a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            } catch (RemoteException e) {
409a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Something went wrong, bail without success
410a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                Log.e(TAG, "Problem persisting user edits", e);
411a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                break;
412a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
413a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            } catch (OperationApplicationException e) {
414a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Version consistency failed, re-parent change and try again
415a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString());
416a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN(");
417a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                boolean first = true;
418a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final int count = state.size();
419a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                for (int i = 0; i < count; i++) {
420a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    Long rawContactId = state.getRawContactId(i);
421a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    if (rawContactId != null && rawContactId != -1) {
422a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        if (!first) {
423a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                            sb.append(',');
424a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        }
425a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        sb.append(rawContactId);
426a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        first = false;
427a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    }
428a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
429a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                sb.append(")");
430a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
431a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (first) {
432a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    throw new IllegalStateException("Version consistency failed for a new contact");
433a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
434a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
435851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu                final RawContactDeltaList newState = RawContactDeltaList.fromQuery(
436c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                        isProfile
437c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                                ? RawContactsEntity.PROFILE_CONTENT_URI
438c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                                : RawContactsEntity.CONTENT_URI,
439c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                        resolver, sb.toString(), null, null);
440851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu                state = RawContactDeltaList.mergeAfter(newState, state);
441c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro
442c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                // Update the new state to use profile URIs if appropriate.
443c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                if (isProfile) {
444851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu                    for (RawContactDelta delta : state) {
445c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                        delta.setProfileQueryUri();
446c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                    }
447c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                }
448a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            }
449a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
450a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
451e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        // Now save any updated photos.  We do this at the end to ensure that
452e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        // the ContactProvider already knows about newly-created contacts.
453e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        if (updatedPhotos != null) {
454e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus            for (String key : updatedPhotos.keySet()) {
455637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee                Uri photoUri = updatedPhotos.getParcelable(key);
456e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                long rawContactId = Long.parseLong(key);
457ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus
458ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                // If the raw-contact ID is negative, we are saving a new raw-contact;
459ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                // replace the bogus ID with the new one that we actually saved the contact at.
460ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                if (rawContactId < 0) {
461ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                    rawContactId = insertedRawContactId;
462ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                    if (rawContactId == -1) {
463ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                        throw new IllegalStateException(
464ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                                "Could not determine RawContact ID for image insertion");
465ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                    }
466ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                }
467ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus
468637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee                if (!saveUpdatedPhoto(rawContactId, photoUri)) succeeded = false;
469e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus            }
470e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        }
471e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
472e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
473e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        if (callbackIntent != null) {
474e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            if (succeeded) {
475e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus                // Mark the intent to indicate that the save was successful (even if the lookup URI
476e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus                // is now null).  For local contacts or the local profile, it's possible that the
477e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus                // save triggered removal of the contact, so no lookup URI would exist..
478e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus                callbackIntent.putExtra(EXTRA_SAVE_SUCCEEDED, true);
479e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            }
480e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            callbackIntent.setData(lookupUri);
481e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            deliverCallback(callbackIntent);
482e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        }
483a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
484a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
485e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    /**
486e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * Save updated photo for the specified raw-contact.
487e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * @return true for success, false for failure
488e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     */
489637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    private boolean saveUpdatedPhoto(long rawContactId, Uri photoUri) {
490ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        final Uri outputUri = Uri.withAppendedPath(
491e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
492e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                RawContacts.DisplayPhoto.CONTENT_DIRECTORY);
493e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
494637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        return ContactPhotoUtils.savePhotoFromUriToUri(this, photoUri, outputUri, true);
495e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    }
496e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
497ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus    /**
498ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus     * Find the ID of an existing or newly-inserted raw-contact.  If none exists, return -1.
499ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus     */
500851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu    private long getRawContactId(RawContactDeltaList state,
501a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            final ArrayList<ContentProviderOperation> diff,
502a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            final ContentProviderResult[] results) {
503ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        long existingRawContactId = state.findRawContactId();
504ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        if (existingRawContactId != -1) {
505ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus            return existingRawContactId;
506a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
507a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
508ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        return getInsertedRawContactId(diff, results);
509ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus    }
510ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus
511ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus    /**
512ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus     * Find the ID of a newly-inserted raw-contact.  If none exists, return -1.
513ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus     */
514ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus    private long getInsertedRawContactId(
515ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus            final ArrayList<ContentProviderOperation> diff,
516ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus            final ContentProviderResult[] results) {
517a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final int diffSize = diff.size();
518a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        for (int i = 0; i < diffSize; i++) {
519a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            ContentProviderOperation operation = diff.get(i);
520a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            if (operation.getType() == ContentProviderOperation.TYPE_INSERT
521a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    && operation.getUri().getEncodedPath().contains(
522a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                            RawContacts.CONTENT_URI.getEncodedPath())) {
523a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                return ContentUris.parseId(results[i].uri);
524a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            }
525a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
526a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return -1;
527a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
528a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
529a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    /**
530717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * Creates an intent that can be sent to this service to create a new group as
531717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * well as add new members at the same time.
532717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     *
533717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param context of the application
534717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param account in which the group should be created
535717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param label is the name of the group (cannot be null)
536717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param rawContactsToAdd is an array of raw contact IDs for contacts that
537717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     *            should be added to the group
538717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param callbackActivity is the activity to send the callback intent to
539717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param callbackAction is the intent action for the callback intent
540caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov     */
5412b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    public static Intent createNewGroupIntent(Context context, AccountWithDataSet account,
542e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            String label, long[] rawContactsToAdd, Class<? extends Activity> callbackActivity,
543717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            String callbackAction) {
5449d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
5459d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP);
5469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
5479d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
5482b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet);
5499d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label);
550717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
551caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
5529d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // Callback intent will be invoked by the service once the new group is
553717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // created.
5549d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
5559d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        callbackIntent.setAction(callbackAction);
5561ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
5579d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
5581ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        return serviceIntent;
5591ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    }
5601ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
5611ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    private void createGroup(Intent intent) {
5622b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
5632b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
5642b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String dataSet = intent.getStringExtra(EXTRA_DATA_SET);
5652b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
566717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
5671ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
5681ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        ContentValues values = new ContentValues();
5691ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.ACCOUNT_TYPE, accountType);
5701ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.ACCOUNT_NAME, accountName);
5712b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        values.put(Groups.DATA_SET, dataSet);
5721ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.TITLE, label);
5731ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
574717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final ContentResolver resolver = getContentResolver();
575717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
576717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Create the new group
577717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values);
578717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
579717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // If there's no URI, then the insertion failed. Abort early because group members can't be
580717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // added if the group doesn't exist
5811ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        if (groupUri == null) {
582717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            Log.e(TAG, "Couldn't create group with label " + label);
5831ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov            return;
5841ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        }
5851ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
586717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Add new group members
587717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri));
588717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
589717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // TODO: Move this into the contact editor where it belongs. This needs to be integrated
590717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // with the way other intent extras that are passed to the {@link ContactEditorActivity}.
5911ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.clear();
5921ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
5931ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri));
5941ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
5951ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
596c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setData(groupUri);
597717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // TODO: This can be taken out when the above TODO is addressed
5981ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values));
5993a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
6001ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    }
6011ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
6021ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    /**
6039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to rename a group.
6041ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov     */
605c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan    public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel,
606e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction) {
6079d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
6089d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP);
6099d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
6109d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
611c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
612c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        // Callback intent will be invoked by the service once the group is renamed.
613c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        Intent callbackIntent = new Intent(context, callbackActivity);
614c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setAction(callbackAction);
615c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
616c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
617caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        return serviceIntent;
618caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
619e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
620e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    private void renameGroup(Intent intent) {
621e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
622e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
623e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
624e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        if (groupId == -1) {
625e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            Log.e(TAG, "Invalid arguments for renameGroup request");
626e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            return;
627e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        }
628e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
629e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        ContentValues values = new ContentValues();
630e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        values.put(Groups.TITLE, label);
631c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
632c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        getContentResolver().update(groupUri, values, null, null);
633c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
634c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
635c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setData(groupUri);
636c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        deliverCallback(callbackIntent);
637e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
638e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
639e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    /**
6409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to delete a group.
641e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov     */
6429d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createGroupDeletionIntent(Context context, long groupId) {
6439d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
6449d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP);
645e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
646e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        return serviceIntent;
647e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
648e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
649e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    private void deleteGroup(Intent intent) {
650e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
651e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        if (groupId == -1) {
652e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            Log.e(TAG, "Invalid arguments for deleteGroup request");
653e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            return;
654e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        }
655e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
656e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        getContentResolver().delete(
657e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov                ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null);
658e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
659e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
660e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    /**
6612d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * Creates an intent that can be sent to this service to rename a group as
6622d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * well as add and remove members from the group.
6632d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *
6642d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param context of the application
6652d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param groupId of the group that should be modified
6662d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param newLabel is the updated name of the group (can be null if the name
6672d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            should not be updated)
6682d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param rawContactsToAdd is an array of raw contact IDs for contacts that
6692d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            should be added to the group
6702d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param rawContactsToRemove is an array of raw contact IDs for contacts
6712d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            that should be removed from the group
6722d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param callbackActivity is the activity to send the callback intent to
6732d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param callbackAction is the intent action for the callback intent
6742d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     */
6752d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel,
6762d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            long[] rawContactsToAdd, long[] rawContactsToRemove,
677e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction) {
6782d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        Intent serviceIntent = new Intent(context, ContactSaveService.class);
6792d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP);
6802d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
6812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
6822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
6832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_REMOVE,
6842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactsToRemove);
6852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
6862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        // Callback intent will be invoked by the service once the group is updated
6872d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        Intent callbackIntent = new Intent(context, callbackActivity);
6882d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        callbackIntent.setAction(callbackAction);
6892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
6902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
6912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        return serviceIntent;
6922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    }
6932d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
6942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    private void updateGroup(Intent intent) {
6952d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
6962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
6972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
6982d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long[] rawContactsToRemove = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_REMOVE);
6992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        if (groupId == -1) {
7012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            Log.e(TAG, "Invalid arguments for updateGroup request");
7022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            return;
7032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
7042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7052d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        final ContentResolver resolver = getContentResolver();
7062d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
7072d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7082d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        // Update group name if necessary
7092d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        if (label != null) {
7102d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            ContentValues values = new ContentValues();
7112d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            values.put(Groups.TITLE, label);
712717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            resolver.update(groupUri, values, null, null);
7132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
7142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
715717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Add and remove members if necessary
716717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        addMembersToGroup(resolver, rawContactsToAdd, groupId);
717717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        removeMembersFromGroup(resolver, rawContactsToRemove, groupId);
718717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
719717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
720717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        callbackIntent.setData(groupUri);
721717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        deliverCallback(callbackIntent);
722717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    }
723717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
72418958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann    private static void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd,
725717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            long groupId) {
726717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        if (rawContactsToAdd == null) {
727717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            return;
728717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        }
7292d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        for (long rawContactId : rawContactsToAdd) {
7302d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            try {
7312d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ArrayList<ContentProviderOperation> rawContactOperations =
7322d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        new ArrayList<ContentProviderOperation>();
7332d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Build an assert operation to ensure the contact is not already in the group
7352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ContentProviderOperation.Builder assertBuilder = ContentProviderOperation
7362d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        .newAssertQuery(Data.CONTENT_URI);
7372d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                assertBuilder.withSelection(Data.RAW_CONTACT_ID + "=? AND " +
7382d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
7392d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        new String[] { String.valueOf(rawContactId),
7402d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
7412d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                assertBuilder.withExpectedCount(0);
7422d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactOperations.add(assertBuilder.build());
7432d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7442d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Build an insert operation to add the contact to the group
7452d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ContentProviderOperation.Builder insertBuilder = ContentProviderOperation
7462d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        .newInsert(Data.CONTENT_URI);
7472d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(Data.RAW_CONTACT_ID, rawContactId);
7482d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
7492d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(GroupMembership.GROUP_ROW_ID, groupId);
7502d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactOperations.add(insertBuilder.build());
7512d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7522d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                if (DEBUG) {
7532d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    for (ContentProviderOperation operation : rawContactOperations) {
7542d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        Log.v(TAG, operation.toString());
7552d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    }
7562d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                }
7572d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7582d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Apply batch
7592d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                if (!rawContactOperations.isEmpty()) {
76018958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann                    resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations);
7612d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                }
7622d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            } catch (RemoteException e) {
7632d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Something went wrong, bail without success
7642d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                Log.e(TAG, "Problem persisting user edits for raw contact ID " +
7652d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(rawContactId), e);
7662d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            } catch (OperationApplicationException e) {
7672d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // The assert could have failed because the contact is already in the group,
7682d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // just continue to the next contact
7692d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                Log.w(TAG, "Assert failed in adding raw contact ID " +
7702d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(rawContactId) + ". Already exists in group " +
7712d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(groupId), e);
7722d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            }
7732d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
774717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    }
7752d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
77618958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann    private static void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove,
777717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            long groupId) {
778717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        if (rawContactsToRemove == null) {
779717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            return;
780717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        }
7812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        for (long rawContactId : rawContactsToRemove) {
7822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // Apply the delete operation on the data row for the given raw contact's
7832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // membership in the given group. If no contact matches the provided selection, then
7842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // nothing will be done. Just continue to the next contact.
78518958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann            resolver.delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " +
7862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
7872d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    new String[] { String.valueOf(rawContactId),
7882d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
7892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
7902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    }
7912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    /**
7939d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to star or un-star a contact.
794e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov     */
7959d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) {
7969d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
7979d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED);
7989d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
7999d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value);
8009d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
801e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        return serviceIntent;
802e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
8039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
8049d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    private void setStarred(Intent intent) {
8059d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
8069d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false);
8079d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        if (contactUri == null) {
8089d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            Log.e(TAG, "Invalid arguments for setStarred request");
8099d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            return;
8109d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        }
8119d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
8129d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        final ContentValues values = new ContentValues(1);
8139d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        values.put(Contacts.STARRED, value);
8149d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        getContentResolver().update(contactUri, values, null, null);
815e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee
816e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        // Undemote the contact if necessary
817e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        final Cursor c = getContentResolver().query(contactUri, new String[] {Contacts._ID},
818e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee                null, null, null);
819e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        try {
820e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee            if (c.moveToFirst()) {
821e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee                final long id = c.getLong(0);
822e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee                values.clear();
823e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee                values.put(String.valueOf(id), PinnedPositions.UNDEMOTE);
824e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee                getContentResolver().update(PinnedPositions.UPDATE_URI, values, null, null);
825e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee            }
826e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        } finally {
827e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee            c.close();
828e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        }
8299d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    }
8300f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
8310f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    /**
832683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     * Creates an intent that can be sent to this service to set the redirect to voicemail.
833683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     */
834683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static Intent createSetSendToVoicemail(Context context, Uri contactUri,
835683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            boolean value) {
836683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Intent serviceIntent = new Intent(context, ContactSaveService.class);
837683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.setAction(ContactSaveService.ACTION_SET_SEND_TO_VOICEMAIL);
838683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
839683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_SEND_TO_VOICEMAIL_FLAG, value);
840683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
841683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        return serviceIntent;
842683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
843683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
844683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    private void setSendToVoicemail(Intent intent) {
845683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
846683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        boolean value = intent.getBooleanExtra(EXTRA_SEND_TO_VOICEMAIL_FLAG, false);
847683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        if (contactUri == null) {
848683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            Log.e(TAG, "Invalid arguments for setRedirectToVoicemail");
849683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            return;
850683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        }
851683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
852683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        final ContentValues values = new ContentValues(1);
853683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        values.put(Contacts.SEND_TO_VOICEMAIL, value);
854683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        getContentResolver().update(contactUri, values, null, null);
855683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
856683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
857683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    /**
858683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     * Creates an intent that can be sent to this service to save the contact's ringtone.
859683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     */
860683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static Intent createSetRingtone(Context context, Uri contactUri,
861683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            String value) {
862683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Intent serviceIntent = new Intent(context, ContactSaveService.class);
863683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.setAction(ContactSaveService.ACTION_SET_RINGTONE);
864683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
865683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CUSTOM_RINGTONE, value);
866683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
867683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        return serviceIntent;
868683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
869683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
870683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    private void setRingtone(Intent intent) {
871683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
872683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        String value = intent.getStringExtra(EXTRA_CUSTOM_RINGTONE);
873683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        if (contactUri == null) {
874683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            Log.e(TAG, "Invalid arguments for setRingtone");
875683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            return;
876683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        }
877683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        ContentValues values = new ContentValues(1);
878683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        values.put(Contacts.CUSTOM_RINGTONE, value);
879683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        getContentResolver().update(contactUri, values, null, null);
880683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
881683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
882683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    /**
8830f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * Creates an intent that sets the selected data item as super primary (default)
8840f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     */
8850f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static Intent createSetSuperPrimaryIntent(Context context, long dataId) {
8860f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        Intent serviceIntent = new Intent(context, ContactSaveService.class);
8870f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY);
8880f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
8890f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        return serviceIntent;
8900f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
8910f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
8920f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    private void setSuperPrimary(Intent intent) {
8930f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
8940f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        if (dataId == -1) {
8950f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            Log.e(TAG, "Invalid arguments for setSuperPrimary request");
8960f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            return;
8970f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        }
8980f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
899d7ca03e23948c3b2d5f97ec5598d8b50e3fc0b25Chiao Cheng        ContactUpdateUtils.setSuperPrimary(this, dataId);
9000f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
9010f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
9020f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    /**
9030f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * Creates an intent that clears the primary flag of all data items that belong to the same
9040f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * raw_contact as the given data item. Will only clear, if the data item was primary before
9050f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * this call
9060f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     */
9070f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static Intent createClearPrimaryIntent(Context context, long dataId) {
9080f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        Intent serviceIntent = new Intent(context, ContactSaveService.class);
9090f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY);
9100f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
9110f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        return serviceIntent;
9120f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
9130f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
9140f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    private void clearPrimary(Intent intent) {
9150f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
9160f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        if (dataId == -1) {
9170f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            Log.e(TAG, "Invalid arguments for clearPrimary request");
9180f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            return;
9190f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        }
9200f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
9210f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        // Update the primary values in the data record.
9220f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        ContentValues values = new ContentValues(1);
9230f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_SUPER_PRIMARY, 0);
9240f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_PRIMARY, 0);
9250f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
9260f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
9270f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann                values, null, null);
9280f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
9297d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
9307d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    /**
9317d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov     * Creates an intent that can be sent to this service to delete a contact.
9327d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov     */
9337d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    public static Intent createDeleteContactIntent(Context context, Uri contactUri) {
9347d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
9357d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT);
9367d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
9377d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        return serviceIntent;
9387d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    }
9397d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
9407d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    private void deleteContact(Intent intent) {
9417d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
9427d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        if (contactUri == null) {
9437d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            Log.e(TAG, "Invalid arguments for deleteContact request");
9447d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            return;
9457d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        }
9467d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
9477d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        getContentResolver().delete(contactUri, null, null);
9487d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    }
9492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    /**
9512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     * Creates an intent that can be sent to this service to join two contacts.
9522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     */
9532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static Intent createJoinContactsIntent(Context context, long contactId1,
9542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            long contactId2, boolean contactWritable,
955e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction) {
9562b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
9572b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS);
9582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1);
9592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2);
9602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_WRITABLE, contactWritable);
9612b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9622b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Callback intent will be invoked by the service once the contacts are joined.
9632b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
9642b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        callbackIntent.setAction(callbackAction);
9652b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
9662b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9672b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        return serviceIntent;
9682b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
9692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9702b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9712b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    private interface JoinContactQuery {
9722b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        String[] PROJECTION = {
9732b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts._ID,
9742b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts.CONTACT_ID,
9752b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts.NAME_VERIFIED,
9762b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts.DISPLAY_NAME_SOURCE,
9772b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        };
9782b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?";
9802b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9812b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int _ID = 0;
9822b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int CONTACT_ID = 1;
9832b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int NAME_VERIFIED = 2;
9842b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int DISPLAY_NAME_SOURCE = 3;
9852b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
9862b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9872b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    private void joinContacts(Intent intent) {
9882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1);
9892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1);
9902b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        boolean writable = intent.getBooleanExtra(EXTRA_CONTACT_WRITABLE, false);
9912b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        if (contactId1 == -1 || contactId2 == -1) {
9922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Log.e(TAG, "Invalid arguments for joinContacts request");
9932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            return;
9942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
9952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        final ContentResolver resolver = getContentResolver();
9972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Load raw contact IDs for all raw contacts involved - currently edited and selected
9992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // in the join UIs
10002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Cursor c = resolver.query(RawContacts.CONTENT_URI,
10012b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                JoinContactQuery.PROJECTION,
10022b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                JoinContactQuery.SELECTION,
10032b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                new String[]{String.valueOf(contactId1), String.valueOf(contactId2)}, null);
10042b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
10052b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        long rawContactIds[];
10062b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        long verifiedNameRawContactId = -1;
10072b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        try {
10087f42b90f870d8df1b12dc90162775ef5f3b31b85Jay Shrauner            if (c.getCount() == 0) {
10097f42b90f870d8df1b12dc90162775ef5f3b31b85Jay Shrauner                return;
10107f42b90f870d8df1b12dc90162775ef5f3b31b85Jay Shrauner            }
10112b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            int maxDisplayNameSource = -1;
10122b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            rawContactIds = new long[c.getCount()];
10132b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            for (int i = 0; i < rawContactIds.length; i++) {
10142b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                c.moveToPosition(i);
10152b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                long rawContactId = c.getLong(JoinContactQuery._ID);
10162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                rawContactIds[i] = rawContactId;
10172b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
10182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                if (nameSource > maxDisplayNameSource) {
10192b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    maxDisplayNameSource = nameSource;
10202b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                }
10212b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            }
10222b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
10232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            // Find an appropriate display name for the joined contact:
10242b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            // if should have a higher DisplayNameSource or be the name
10252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            // of the original contact that we are joining with another.
10262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            if (writable) {
10272b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                for (int i = 0; i < rawContactIds.length; i++) {
10282b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    c.moveToPosition(i);
10292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    if (c.getLong(JoinContactQuery.CONTACT_ID) == contactId1) {
10302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                        int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
10312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                        if (nameSource == maxDisplayNameSource
10322b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                                && (verifiedNameRawContactId == -1
10332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                                        || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0)) {
10342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                            verifiedNameRawContactId = c.getLong(JoinContactQuery._ID);
10352b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                        }
10362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    }
10372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                }
10382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            }
10392b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } finally {
10402b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            c.close();
10412b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
10422b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
10432b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // For each pair of raw contacts, insert an aggregation exception
10442b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
10452b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        for (int i = 0; i < rawContactIds.length; i++) {
10462b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            for (int j = 0; j < rawContactIds.length; j++) {
10472b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                if (i != j) {
10482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
10492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                }
10502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            }
10512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
10522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
10532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Mark the original contact as "name verified" to make sure that the contact
10542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // display name does not change as a result of the join
10552b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        if (verifiedNameRawContactId != -1) {
10562b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Builder builder = ContentProviderOperation.newUpdate(
10572b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId));
10582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            builder.withValue(RawContacts.NAME_VERIFIED, 1);
10592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            operations.add(builder.build());
10602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
10612b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
10622b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        boolean success = false;
10632b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Apply all aggregation exceptions as one batch
10642b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        try {
10652b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            resolver.applyBatch(ContactsContract.AUTHORITY, operations);
1066886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            showToast(R.string.contactsJoinedMessage);
10672b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            success = true;
10682b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } catch (RemoteException e) {
10692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Log.e(TAG, "Failed to apply aggregation exception batch", e);
1070886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            showToast(R.string.contactSavedErrorToast);
10712b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } catch (OperationApplicationException e) {
10722b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Log.e(TAG, "Failed to apply aggregation exception batch", e);
1073886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            showToast(R.string.contactSavedErrorToast);
10742b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
10752b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
10762b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
10772b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        if (success) {
10782b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Uri uri = RawContacts.getContactLookupUri(resolver,
10792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
10802b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            callbackIntent.setData(uri);
10812b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
10823a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
10832b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
10842b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
10852b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    /**
10862b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation.
10872b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     */
10882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations,
10892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            long rawContactId1, long rawContactId2) {
10902b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Builder builder =
10912b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
10922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
10932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
10942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
10952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        operations.add(builder.build());
10962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
1097886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov
1098886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    /**
1099886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov     * Shows a toast on the UI thread.
1100886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov     */
1101886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    private void showToast(final int message) {
11023a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler.post(new Runnable() {
1103886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov
1104886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            @Override
1105886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            public void run() {
1106886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov                Toast.makeText(ContactSaveService.this, message, Toast.LENGTH_LONG).show();
1107886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            }
1108886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov        });
1109886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    }
11103a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
11113a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    private void deliverCallback(final Intent callbackIntent) {
11123a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler.post(new Runnable() {
11133a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
11143a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            @Override
11153a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            public void run() {
11163a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                deliverCallbackOnUiThread(callbackIntent);
11173a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            }
11183a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        });
11193a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
11203a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
11213a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    void deliverCallbackOnUiThread(final Intent callbackIntent) {
11223a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // TODO: this assumes that if there are multiple instances of the same
11233a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // activity registered, the last one registered is the one waiting for
11243a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // the callback. Validity of this assumption needs to be verified.
1125a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson        for (Listener listener : sListeners) {
1126a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson            if (callbackIntent.getComponent().equals(
1127a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson                    ((Activity) listener).getIntent().getComponent())) {
1128a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson                listener.onServiceCompleted(callbackIntent);
1129a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson                return;
11303a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            }
11313a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        }
11323a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1133173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann}
1134