ContactSaveService.java revision 162b7e34fb581d0fa279957af5136d190c40759f
1173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann/*
2173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * Copyright (C) 2010 The Android Open Source Project
3173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann *
4173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * Licensed under the Apache License, Version 2.0 (the "License");
5173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * you may not use this file except in compliance with the License.
6173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * You may obtain a copy of the License at
7173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann *
8173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann *      http://www.apache.org/licenses/LICENSE-2.0
9173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann *
10173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * Unless required by applicable law or agreed to in writing, software
11173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * distributed under the License is distributed on an "AS IS" BASIS,
12173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * See the License for the specific language governing permissions and
14173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * limitations under the License.
15173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann */
16173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
1718ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikovpackage com.android.contacts;
18173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
19a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport com.android.contacts.model.AccountTypeManager;
202b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoroimport com.android.contacts.model.AccountWithDataSet;
21c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoroimport com.android.contacts.model.EntityDelta;
22a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport com.android.contacts.model.EntityDeltaList;
23a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport com.android.contacts.model.EntityModifier;
241ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikovimport com.google.android.collect.Lists;
25caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport com.google.android.collect.Sets;
26caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
273a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikovimport android.app.Activity;
28173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.app.IntentService;
29173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.content.ContentProviderOperation;
302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.content.ContentProviderOperation.Builder;
31caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentProviderResult;
32caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentResolver;
33e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikovimport android.content.ContentUris;
34caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentValues;
359d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikovimport android.content.Context;
36173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.content.Intent;
372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.content.OperationApplicationException;
382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.database.Cursor;
39caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.net.Uri;
40886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikovimport android.os.Handler;
41886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikovimport android.os.Looper;
42a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport android.os.Parcelable;
432b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.os.RemoteException;
44173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.provider.ContactsContract;
452b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.provider.ContactsContract.AggregationExceptions;
461ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.GroupMembership;
479d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikovimport android.provider.ContactsContract.Contacts;
48caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.Data;
49e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikovimport android.provider.ContactsContract.Groups;
50ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelsonimport android.provider.ContactsContract.Profile;
51caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.RawContacts;
52c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoroimport android.provider.ContactsContract.RawContactsEntity;
53173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.util.Log;
542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.widget.Toast;
55173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
56173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport java.util.ArrayList;
57caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport java.util.HashSet;
58caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport java.util.List;
59a831c0b539cdc120655856074d4621e8e60a843bHugo Hudsonimport java.util.concurrent.CopyOnWriteArrayList;
60173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
6118ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov/**
6218ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov * A service responsible for saving changes to the content provider.
6318ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov */
64173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannpublic class ContactSaveService extends IntentService {
65173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    private static final String TAG = "ContactSaveService";
66173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
67a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan    /** Set to true in order to view logs on content provider operations */
68a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan    private static final boolean DEBUG = false;
69a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan
70caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String ACTION_NEW_RAW_CONTACT = "newRawContact";
71caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
72caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_ACCOUNT_NAME = "accountName";
73caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_ACCOUNT_TYPE = "accountType";
742b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    public static final String EXTRA_DATA_SET = "dataSet";
75caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_CONTENT_VALUES = "contentValues";
76caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_CALLBACK_INTENT = "callbackIntent";
77caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
78a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String ACTION_SAVE_CONTACT = "saveContact";
79a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String EXTRA_CONTACT_STATE = "state";
80a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String EXTRA_SAVE_MODE = "saveMode";
81ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson    public static final String EXTRA_SAVE_IS_PROFILE = "saveIsProfile";
82173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
831ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    public static final String ACTION_CREATE_GROUP = "createGroup";
84e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String ACTION_RENAME_GROUP = "renameGroup";
85e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String ACTION_DELETE_GROUP = "deleteGroup";
862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String ACTION_UPDATE_GROUP = "updateGroup";
87e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String EXTRA_GROUP_ID = "groupId";
88e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String EXTRA_GROUP_LABEL = "groupLabel";
892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String EXTRA_RAW_CONTACTS_TO_ADD = "rawContactsToAdd";
902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String EXTRA_RAW_CONTACTS_TO_REMOVE = "rawContactsToRemove";
91e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
929d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String ACTION_SET_STARRED = "setStarred";
937d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    public static final String ACTION_DELETE_CONTACT = "delete";
949d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String EXTRA_CONTACT_URI = "contactUri";
959d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String EXTRA_STARRED_FLAG = "starred";
969d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
970f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary";
980f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String ACTION_CLEAR_PRIMARY = "clearPrimary";
990f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String EXTRA_DATA_ID = "dataId";
1000f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
1012b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String ACTION_JOIN_CONTACTS = "joinContacts";
1022b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String EXTRA_CONTACT_ID1 = "contactId1";
1032b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String EXTRA_CONTACT_ID2 = "contactId2";
1042b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String EXTRA_CONTACT_WRITABLE = "contactWritable";
1052b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
106683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String ACTION_SET_SEND_TO_VOICEMAIL = "sendToVoicemail";
107683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String EXTRA_SEND_TO_VOICEMAIL_FLAG = "sendToVoicemailFlag";
108683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
109683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String ACTION_SET_RINGTONE = "setRingtone";
110683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String EXTRA_CUSTOM_RINGTONE = "customRingtone";
111683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
112caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet(
113caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.MIMETYPE,
114caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.IS_PRIMARY,
115caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA1,
116caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA2,
117caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA3,
118caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA4,
119caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA5,
120caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA6,
121caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA7,
122caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA8,
123caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA9,
124caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA10,
125caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA11,
126caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA12,
127caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA13,
128caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA14,
129caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA15
130caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    );
131caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
132a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    private static final int PERSIST_TRIES = 3;
133a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
1343a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public interface Listener {
1353a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        public void onServiceCompleted(Intent callbackIntent);
1363a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1373a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
138a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson    private static final CopyOnWriteArrayList<Listener> sListeners =
139a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson            new CopyOnWriteArrayList<Listener>();
1403a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1413a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    private Handler mMainHandler;
1423a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
143173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    public ContactSaveService() {
144173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann        super(TAG);
145173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann        setIntentRedelivery(true);
1463a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler = new Handler(Looper.getMainLooper());
1473a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1483a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1493a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public static void registerListener(Listener listener) {
1503a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        if (!(listener instanceof Activity)) {
1513a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            throw new ClassCastException("Only activities can be registered to"
1523a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                    + " receive callback from " + ContactSaveService.class.getName());
1533a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        }
154a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson        sListeners.add(0, listener);
1553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1563a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1573a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public static void unregisterListener(Listener listener) {
158a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson        sListeners.remove(listener);
159173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    }
160173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
161173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    @Override
162a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public Object getSystemService(String name) {
163a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Object service = super.getSystemService(name);
164a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        if (service != null) {
165a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            return service;
166a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
167a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
168a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return getApplicationContext().getSystemService(name);
169a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
170a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
171a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    @Override
172173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    protected void onHandleIntent(Intent intent) {
173caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String action = intent.getAction();
174caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        if (ACTION_NEW_RAW_CONTACT.equals(action)) {
175caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            createRawContact(intent);
176a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        } else if (ACTION_SAVE_CONTACT.equals(action)) {
177a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            saveContact(intent);
1781ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        } else if (ACTION_CREATE_GROUP.equals(action)) {
1791ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov            createGroup(intent);
180e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        } else if (ACTION_RENAME_GROUP.equals(action)) {
181e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            renameGroup(intent);
182e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        } else if (ACTION_DELETE_GROUP.equals(action)) {
183e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            deleteGroup(intent);
1842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        } else if (ACTION_UPDATE_GROUP.equals(action)) {
1852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            updateGroup(intent);
1869d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        } else if (ACTION_SET_STARRED.equals(action)) {
1879d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            setStarred(intent);
1880f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) {
1890f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            setSuperPrimary(intent);
1900f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        } else if (ACTION_CLEAR_PRIMARY.equals(action)) {
1910f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            clearPrimary(intent);
1927d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        } else if (ACTION_DELETE_CONTACT.equals(action)) {
1937d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            deleteContact(intent);
1942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } else if (ACTION_JOIN_CONTACTS.equals(action)) {
1952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            joinContacts(intent);
196683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) {
197683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            setSendToVoicemail(intent);
198683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        } else if (ACTION_SET_RINGTONE.equals(action)) {
199683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            setRingtone(intent);
200caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
201caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
202caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
2039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    /**
2049d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to create a new raw contact
2059d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * using data presented as a set of ContentValues.
2069d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     */
2079d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createNewRawContactIntent(Context context,
2082b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            ArrayList<ContentValues> values, AccountWithDataSet account,
2092b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            Class<?> callbackActivity, String callbackAction) {
2109d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(
2119d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov                context, ContactSaveService.class);
2129d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT);
2139d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        if (account != null) {
2149d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
2159d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
2162b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet);
2179d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        }
2189d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putParcelableArrayListExtra(
2199d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov                ContactSaveService.EXTRA_CONTENT_VALUES, values);
2209d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
2219d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // Callback intent will be invoked by the service once the new contact is
2229d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // created.  The service will put the URI of the new contact as "data" on
2239d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // the callback intent.
2249d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
2259d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        callbackIntent.setAction(callbackAction);
2269d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
2279d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        return serviceIntent;
2289d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    }
2299d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
230caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    private void createRawContact(Intent intent) {
231caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
232caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
2332b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String dataSet = intent.getStringExtra(EXTRA_DATA_SET);
234caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES);
235caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
236caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
237caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
238caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
239caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .withValue(RawContacts.ACCOUNT_NAME, accountName)
240caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .withValue(RawContacts.ACCOUNT_TYPE, accountType)
2412b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                .withValue(RawContacts.DATA_SET, dataSet)
242caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .build());
243caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
244caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        int size = valueList.size();
245caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        for (int i = 0; i < size; i++) {
246caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            ContentValues values = valueList.get(i);
247caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            values.keySet().retainAll(ALLOWED_DATA_COLUMNS);
248caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
249caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .withValueBackReference(Data.RAW_CONTACT_ID, 0)
250caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .withValues(values)
251caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .build());
252caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
253caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
254caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ContentResolver resolver = getContentResolver();
255caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ContentProviderResult[] results;
256caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        try {
257caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            results = resolver.applyBatch(ContactsContract.AUTHORITY, operations);
258caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        } catch (Exception e) {
259caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            throw new RuntimeException("Failed to store new contact", e);
260caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
261caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
262caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Uri rawContactUri = results[0].uri;
263caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri));
264caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
2653a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
266caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
267caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
268caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    /**
269a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     * Creates an intent that can be sent to this service to create a new raw contact
270a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     * using data presented as a set of ContentValues.
271a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     */
272a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static Intent createSaveContactIntent(Context context, EntityDeltaList state,
273ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson            String saveModeExtraKey, int saveMode, boolean isProfile, Class<?> callbackActivity,
274a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            String callbackAction) {
275a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Intent serviceIntent = new Intent(
276a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                context, ContactSaveService.class);
277a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT);
278a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state);
279ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson        serviceIntent.putExtra(EXTRA_SAVE_IS_PROFILE, isProfile);
280a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
281a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // Callback intent will be invoked by the service once the contact is
282a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // saved.  The service will put the URI of the new contact as "data" on
283a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // the callback intent.
284a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
285a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        callbackIntent.putExtra(saveModeExtraKey, saveMode);
286a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        callbackIntent.setAction(callbackAction);
287a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
288a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return serviceIntent;
289a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
290a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
291a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    private void saveContact(Intent intent) {
292a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        EntityDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE);
293a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
294ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson        boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false);
295a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
296a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // Trim any empty fields, and RawContacts, before persisting
297a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
298a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        EntityModifier.trimEmpty(state, accountTypes);
299a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
300a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Uri lookupUri = null;
301a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
302a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final ContentResolver resolver = getContentResolver();
303a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
304a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // Attempt to persist changes
305a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        int tries = 0;
306a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        while (tries++ < PERSIST_TRIES) {
307a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            try {
308a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Build operations and try applying
309a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final ArrayList<ContentProviderOperation> diff = state.buildDiff();
310a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                if (DEBUG) {
311a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    Log.v(TAG, "Content Provider Operations:");
312a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    for (ContentProviderOperation operation : diff) {
313a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                        Log.v(TAG, operation.toString());
314a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    }
315a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                }
316a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan
317a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                ContentProviderResult[] results = null;
318a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (!diff.isEmpty()) {
319a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
320a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
321a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
322a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final long rawContactId = getRawContactId(state, diff, results);
323a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (rawContactId == -1) {
324a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    throw new IllegalStateException("Could not determine RawContact ID after save");
325a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
3267c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                if (isProfile) {
3277c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    // Since the profile supports local raw contacts, which may have been completely
3287c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    // removed if all information was removed, we need to do a special query to
3297c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    // get the lookup URI for the profile contact (if it still exists).
3307c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    Cursor c = resolver.query(Profile.CONTENT_URI,
3317c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                            new String[] {Contacts._ID, Contacts.LOOKUP_KEY},
3327c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                            null, null, null);
3337c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    try {
334162b7e34fb581d0fa279957af5136d190c40759fErik                        if (c.moveToFirst()) {
335162b7e34fb581d0fa279957af5136d190c40759fErik                            final long contactId = c.getLong(0);
336162b7e34fb581d0fa279957af5136d190c40759fErik                            final String lookupKey = c.getString(1);
337162b7e34fb581d0fa279957af5136d190c40759fErik                            lookupUri = Contacts.getLookupUri(contactId, lookupKey);
338162b7e34fb581d0fa279957af5136d190c40759fErik                        }
3397c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    } finally {
3407c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                        c.close();
3417c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    }
3427c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                } else {
3437c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
3447c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                                    rawContactId);
3457c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri);
3467c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                }
347a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                Log.v(TAG, "Saved contact. New URI: " + lookupUri);
348a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                break;
349a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
350a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            } catch (RemoteException e) {
351a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Something went wrong, bail without success
352a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                Log.e(TAG, "Problem persisting user edits", e);
353a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                break;
354a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
355a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            } catch (OperationApplicationException e) {
356a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Version consistency failed, re-parent change and try again
357a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString());
358a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN(");
359a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                boolean first = true;
360a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final int count = state.size();
361a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                for (int i = 0; i < count; i++) {
362a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    Long rawContactId = state.getRawContactId(i);
363a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    if (rawContactId != null && rawContactId != -1) {
364a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        if (!first) {
365a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                            sb.append(',');
366a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        }
367a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        sb.append(rawContactId);
368a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        first = false;
369a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    }
370a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
371a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                sb.append(")");
372a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
373a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (first) {
374a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    throw new IllegalStateException("Version consistency failed for a new contact");
375a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
376a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
377c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                final EntityDeltaList newState = EntityDeltaList.fromQuery(
378c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                        isProfile
379c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                                ? RawContactsEntity.PROFILE_CONTENT_URI
380c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                                : RawContactsEntity.CONTENT_URI,
381c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                        resolver, sb.toString(), null, null);
382a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                state = EntityDeltaList.mergeAfter(newState, state);
383c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro
384c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                // Update the new state to use profile URIs if appropriate.
385c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                if (isProfile) {
386c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                    for (EntityDelta delta : state) {
387c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                        delta.setProfileQueryUri();
388c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                    }
389c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                }
390a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            }
391a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
392a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
393a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        callbackIntent.setData(lookupUri);
394a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
3953a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
396a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
397a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
398a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    private long getRawContactId(EntityDeltaList state,
399a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            final ArrayList<ContentProviderOperation> diff,
400a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            final ContentProviderResult[] results) {
401a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        long rawContactId = state.findRawContactId();
402a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        if (rawContactId != -1) {
403a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            return rawContactId;
404a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
405a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
406a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final int diffSize = diff.size();
407a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        for (int i = 0; i < diffSize; i++) {
408a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            ContentProviderOperation operation = diff.get(i);
409a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            if (operation.getType() == ContentProviderOperation.TYPE_INSERT
410a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    && operation.getUri().getEncodedPath().contains(
411a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                            RawContacts.CONTENT_URI.getEncodedPath())) {
412a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                return ContentUris.parseId(results[i].uri);
413a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            }
414a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
415a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return -1;
416a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
417a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
418a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    /**
419717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * Creates an intent that can be sent to this service to create a new group as
420717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * well as add new members at the same time.
421717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     *
422717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param context of the application
423717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param account in which the group should be created
424717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param label is the name of the group (cannot be null)
425717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param rawContactsToAdd is an array of raw contact IDs for contacts that
426717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     *            should be added to the group
427717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param callbackActivity is the activity to send the callback intent to
428717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param callbackAction is the intent action for the callback intent
429caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov     */
4302b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    public static Intent createNewGroupIntent(Context context, AccountWithDataSet account,
431717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            String label, long[] rawContactsToAdd, Class<?> callbackActivity,
432717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            String callbackAction) {
4339d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
4349d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP);
4359d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
4369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
4372b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet);
4389d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label);
439717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
440caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
4419d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // Callback intent will be invoked by the service once the new group is
442717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // created.
4439d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
4449d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        callbackIntent.setAction(callbackAction);
4451ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
4469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
4471ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        return serviceIntent;
4481ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    }
4491ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
4501ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    private void createGroup(Intent intent) {
4512b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
4522b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
4532b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String dataSet = intent.getStringExtra(EXTRA_DATA_SET);
4542b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
455717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
4561ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
4571ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        ContentValues values = new ContentValues();
4581ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.ACCOUNT_TYPE, accountType);
4591ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.ACCOUNT_NAME, accountName);
4602b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        values.put(Groups.DATA_SET, dataSet);
4611ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.TITLE, label);
4621ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
463717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final ContentResolver resolver = getContentResolver();
464717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
465717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Create the new group
466717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values);
467717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
468717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // If there's no URI, then the insertion failed. Abort early because group members can't be
469717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // added if the group doesn't exist
4701ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        if (groupUri == null) {
471717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            Log.e(TAG, "Couldn't create group with label " + label);
4721ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov            return;
4731ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        }
4741ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
475717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Add new group members
476717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri));
477717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
478717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // TODO: Move this into the contact editor where it belongs. This needs to be integrated
479717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // with the way other intent extras that are passed to the {@link ContactEditorActivity}.
4801ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.clear();
4811ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
4821ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri));
4831ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
4841ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
485c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setData(groupUri);
486717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // TODO: This can be taken out when the above TODO is addressed
4871ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values));
4883a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
4891ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    }
4901ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
4911ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    /**
4929d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to rename a group.
4931ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov     */
494c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan    public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel,
495c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan            Class<?> callbackActivity, String callbackAction) {
4969d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
4979d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP);
4989d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
4999d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
500c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
501c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        // Callback intent will be invoked by the service once the group is renamed.
502c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        Intent callbackIntent = new Intent(context, callbackActivity);
503c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setAction(callbackAction);
504c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
505c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
506caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        return serviceIntent;
507caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
508e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
509e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    private void renameGroup(Intent intent) {
510e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
511e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
512e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
513e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        if (groupId == -1) {
514e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            Log.e(TAG, "Invalid arguments for renameGroup request");
515e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            return;
516e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        }
517e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
518e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        ContentValues values = new ContentValues();
519e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        values.put(Groups.TITLE, label);
520c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
521c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        getContentResolver().update(groupUri, values, null, null);
522c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
523c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
524c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setData(groupUri);
525c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        deliverCallback(callbackIntent);
526e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
527e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
528e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    /**
5299d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to delete a group.
530e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov     */
5319d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createGroupDeletionIntent(Context context, long groupId) {
5329d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
5339d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP);
534e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
535e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        return serviceIntent;
536e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
537e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
538e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    private void deleteGroup(Intent intent) {
539e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
540e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        if (groupId == -1) {
541e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            Log.e(TAG, "Invalid arguments for deleteGroup request");
542e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            return;
543e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        }
544e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
545e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        getContentResolver().delete(
546e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov                ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null);
547e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
548e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
549e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    /**
5502d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * Creates an intent that can be sent to this service to rename a group as
5512d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * well as add and remove members from the group.
5522d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *
5532d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param context of the application
5542d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param groupId of the group that should be modified
5552d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param newLabel is the updated name of the group (can be null if the name
5562d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            should not be updated)
5572d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param rawContactsToAdd is an array of raw contact IDs for contacts that
5582d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            should be added to the group
5592d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param rawContactsToRemove is an array of raw contact IDs for contacts
5602d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            that should be removed from the group
5612d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param callbackActivity is the activity to send the callback intent to
5622d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param callbackAction is the intent action for the callback intent
5632d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     */
5642d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel,
5652d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            long[] rawContactsToAdd, long[] rawContactsToRemove,
5662d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            Class<?> callbackActivity, String callbackAction) {
5672d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        Intent serviceIntent = new Intent(context, ContactSaveService.class);
5682d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP);
5692d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
5702d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
5712d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
5722d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_REMOVE,
5732d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactsToRemove);
5742d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5752d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        // Callback intent will be invoked by the service once the group is updated
5762d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        Intent callbackIntent = new Intent(context, callbackActivity);
5772d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        callbackIntent.setAction(callbackAction);
5782d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
5792d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5802d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        return serviceIntent;
5812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    }
5822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    private void updateGroup(Intent intent) {
5842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
5852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
5862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
5872d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long[] rawContactsToRemove = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_REMOVE);
5882d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        if (groupId == -1) {
5902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            Log.e(TAG, "Invalid arguments for updateGroup request");
5912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            return;
5922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
5932d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        final ContentResolver resolver = getContentResolver();
5952d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
5962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        // Update group name if necessary
5982d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        if (label != null) {
5992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            ContentValues values = new ContentValues();
6002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            values.put(Groups.TITLE, label);
601717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            resolver.update(groupUri, values, null, null);
6022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
6032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
604717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Add and remove members if necessary
605717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        addMembersToGroup(resolver, rawContactsToAdd, groupId);
606717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        removeMembersFromGroup(resolver, rawContactsToRemove, groupId);
607717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
608717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
609717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        callbackIntent.setData(groupUri);
610717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        deliverCallback(callbackIntent);
611717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    }
612717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
613717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    private void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd,
614717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            long groupId) {
615717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        if (rawContactsToAdd == null) {
616717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            return;
617717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        }
6182d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        for (long rawContactId : rawContactsToAdd) {
6192d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            try {
6202d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ArrayList<ContentProviderOperation> rawContactOperations =
6212d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        new ArrayList<ContentProviderOperation>();
6222d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
6232d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Build an assert operation to ensure the contact is not already in the group
6242d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ContentProviderOperation.Builder assertBuilder = ContentProviderOperation
6252d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        .newAssertQuery(Data.CONTENT_URI);
6262d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                assertBuilder.withSelection(Data.RAW_CONTACT_ID + "=? AND " +
6272d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
6282d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        new String[] { String.valueOf(rawContactId),
6292d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
6302d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                assertBuilder.withExpectedCount(0);
6312d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactOperations.add(assertBuilder.build());
6322d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
6332d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Build an insert operation to add the contact to the group
6342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ContentProviderOperation.Builder insertBuilder = ContentProviderOperation
6352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        .newInsert(Data.CONTENT_URI);
6362d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(Data.RAW_CONTACT_ID, rawContactId);
6372d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
6382d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(GroupMembership.GROUP_ROW_ID, groupId);
6392d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactOperations.add(insertBuilder.build());
6402d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
6412d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                if (DEBUG) {
6422d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    for (ContentProviderOperation operation : rawContactOperations) {
6432d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        Log.v(TAG, operation.toString());
6442d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    }
6452d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                }
6462d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
6472d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Apply batch
6482d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                ContentProviderResult[] results = null;
6492d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                if (!rawContactOperations.isEmpty()) {
6502d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    results = resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations);
6512d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                }
6522d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            } catch (RemoteException e) {
6532d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Something went wrong, bail without success
6542d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                Log.e(TAG, "Problem persisting user edits for raw contact ID " +
6552d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(rawContactId), e);
6562d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            } catch (OperationApplicationException e) {
6572d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // The assert could have failed because the contact is already in the group,
6582d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // just continue to the next contact
6592d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                Log.w(TAG, "Assert failed in adding raw contact ID " +
6602d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(rawContactId) + ". Already exists in group " +
6612d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(groupId), e);
6622d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            }
6632d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
664717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    }
6652d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
666717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    private void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove,
667717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            long groupId) {
668717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        if (rawContactsToRemove == null) {
669717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            return;
670717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        }
6712d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        for (long rawContactId : rawContactsToRemove) {
6722d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // Apply the delete operation on the data row for the given raw contact's
6732d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // membership in the given group. If no contact matches the provided selection, then
6742d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // nothing will be done. Just continue to the next contact.
6752d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            getContentResolver().delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " +
6762d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
6772d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    new String[] { String.valueOf(rawContactId),
6782d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
6792d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
6802d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    }
6812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
6822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    /**
6839d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to star or un-star a contact.
684e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov     */
6859d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) {
6869d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
6879d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED);
6889d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
6899d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value);
6909d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
691e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        return serviceIntent;
692e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
6939d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
6949d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    private void setStarred(Intent intent) {
6959d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
6969d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false);
6979d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        if (contactUri == null) {
6989d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            Log.e(TAG, "Invalid arguments for setStarred request");
6999d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            return;
7009d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        }
7019d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
7029d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        final ContentValues values = new ContentValues(1);
7039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        values.put(Contacts.STARRED, value);
7049d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        getContentResolver().update(contactUri, values, null, null);
7059d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    }
7060f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
7070f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    /**
708683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     * Creates an intent that can be sent to this service to set the redirect to voicemail.
709683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     */
710683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static Intent createSetSendToVoicemail(Context context, Uri contactUri,
711683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            boolean value) {
712683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Intent serviceIntent = new Intent(context, ContactSaveService.class);
713683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.setAction(ContactSaveService.ACTION_SET_SEND_TO_VOICEMAIL);
714683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
715683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_SEND_TO_VOICEMAIL_FLAG, value);
716683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
717683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        return serviceIntent;
718683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
719683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
720683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    private void setSendToVoicemail(Intent intent) {
721683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
722683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        boolean value = intent.getBooleanExtra(EXTRA_SEND_TO_VOICEMAIL_FLAG, false);
723683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        if (contactUri == null) {
724683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            Log.e(TAG, "Invalid arguments for setRedirectToVoicemail");
725683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            return;
726683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        }
727683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
728683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        final ContentValues values = new ContentValues(1);
729683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        values.put(Contacts.SEND_TO_VOICEMAIL, value);
730683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        getContentResolver().update(contactUri, values, null, null);
731683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
732683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
733683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    /**
734683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     * Creates an intent that can be sent to this service to save the contact's ringtone.
735683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     */
736683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static Intent createSetRingtone(Context context, Uri contactUri,
737683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            String value) {
738683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Intent serviceIntent = new Intent(context, ContactSaveService.class);
739683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.setAction(ContactSaveService.ACTION_SET_RINGTONE);
740683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
741683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CUSTOM_RINGTONE, value);
742683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
743683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        return serviceIntent;
744683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
745683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
746683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    private void setRingtone(Intent intent) {
747683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
748683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        String value = intent.getStringExtra(EXTRA_CUSTOM_RINGTONE);
749683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        if (contactUri == null) {
750683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            Log.e(TAG, "Invalid arguments for setRingtone");
751683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            return;
752683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        }
753683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        ContentValues values = new ContentValues(1);
754683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        values.put(Contacts.CUSTOM_RINGTONE, value);
755683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        getContentResolver().update(contactUri, values, null, null);
756683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
757683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
758683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    /**
7590f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * Creates an intent that sets the selected data item as super primary (default)
7600f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     */
7610f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static Intent createSetSuperPrimaryIntent(Context context, long dataId) {
7620f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        Intent serviceIntent = new Intent(context, ContactSaveService.class);
7630f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY);
7640f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
7650f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        return serviceIntent;
7660f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
7670f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
7680f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    private void setSuperPrimary(Intent intent) {
7690f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
7700f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        if (dataId == -1) {
7710f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            Log.e(TAG, "Invalid arguments for setSuperPrimary request");
7720f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            return;
7730f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        }
7740f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
7750f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        // Update the primary values in the data record.
7760f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        ContentValues values = new ContentValues(1);
7770f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_SUPER_PRIMARY, 1);
7780f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_PRIMARY, 1);
7790f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
7800f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
7810f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann                values, null, null);
7820f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
7830f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
7840f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    /**
7850f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * Creates an intent that clears the primary flag of all data items that belong to the same
7860f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * raw_contact as the given data item. Will only clear, if the data item was primary before
7870f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * this call
7880f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     */
7890f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static Intent createClearPrimaryIntent(Context context, long dataId) {
7900f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        Intent serviceIntent = new Intent(context, ContactSaveService.class);
7910f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY);
7920f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
7930f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        return serviceIntent;
7940f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
7950f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
7960f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    private void clearPrimary(Intent intent) {
7970f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
7980f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        if (dataId == -1) {
7990f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            Log.e(TAG, "Invalid arguments for clearPrimary request");
8000f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            return;
8010f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        }
8020f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
8030f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        // Update the primary values in the data record.
8040f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        ContentValues values = new ContentValues(1);
8050f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_SUPER_PRIMARY, 0);
8060f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_PRIMARY, 0);
8070f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
8080f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
8090f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann                values, null, null);
8100f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
8117d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
8127d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    /**
8137d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov     * Creates an intent that can be sent to this service to delete a contact.
8147d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov     */
8157d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    public static Intent createDeleteContactIntent(Context context, Uri contactUri) {
8167d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
8177d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT);
8187d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
8197d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        return serviceIntent;
8207d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    }
8217d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
8227d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    private void deleteContact(Intent intent) {
8237d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
8247d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        if (contactUri == null) {
8257d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            Log.e(TAG, "Invalid arguments for deleteContact request");
8267d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            return;
8277d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        }
8287d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
8297d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        getContentResolver().delete(contactUri, null, null);
8307d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    }
8312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8322b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    /**
8332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     * Creates an intent that can be sent to this service to join two contacts.
8342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     */
8352b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static Intent createJoinContactsIntent(Context context, long contactId1,
8362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            long contactId2, boolean contactWritable,
8372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Class<?> callbackActivity, String callbackAction) {
8382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
8392b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS);
8402b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1);
8412b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2);
8422b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_WRITABLE, contactWritable);
8432b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8442b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Callback intent will be invoked by the service once the contacts are joined.
8452b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
8462b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        callbackIntent.setAction(callbackAction);
8472b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
8482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        return serviceIntent;
8502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
8512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    private interface JoinContactQuery {
8542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        String[] PROJECTION = {
8552b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts._ID,
8562b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts.CONTACT_ID,
8572b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts.NAME_VERIFIED,
8582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts.DISPLAY_NAME_SOURCE,
8592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        };
8602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8612b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?";
8622b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8632b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int _ID = 0;
8642b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int CONTACT_ID = 1;
8652b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int NAME_VERIFIED = 2;
8662b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int DISPLAY_NAME_SOURCE = 3;
8672b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
8682b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    private void joinContacts(Intent intent) {
8702b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1);
8712b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1);
8722b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        boolean writable = intent.getBooleanExtra(EXTRA_CONTACT_WRITABLE, false);
8732b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        if (contactId1 == -1 || contactId2 == -1) {
8742b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Log.e(TAG, "Invalid arguments for joinContacts request");
8752b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            return;
8762b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
8772b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8782b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        final ContentResolver resolver = getContentResolver();
8792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8802b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Load raw contact IDs for all raw contacts involved - currently edited and selected
8812b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // in the join UIs
8822b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Cursor c = resolver.query(RawContacts.CONTENT_URI,
8832b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                JoinContactQuery.PROJECTION,
8842b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                JoinContactQuery.SELECTION,
8852b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                new String[]{String.valueOf(contactId1), String.valueOf(contactId2)}, null);
8862b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8872b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        long rawContactIds[];
8882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        long verifiedNameRawContactId = -1;
8892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        try {
8902b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            int maxDisplayNameSource = -1;
8912b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            rawContactIds = new long[c.getCount()];
8922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            for (int i = 0; i < rawContactIds.length; i++) {
8932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                c.moveToPosition(i);
8942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                long rawContactId = c.getLong(JoinContactQuery._ID);
8952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                rawContactIds[i] = rawContactId;
8962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
8972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                if (nameSource > maxDisplayNameSource) {
8982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    maxDisplayNameSource = nameSource;
8992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                }
9002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            }
9012b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9022b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            // Find an appropriate display name for the joined contact:
9032b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            // if should have a higher DisplayNameSource or be the name
9042b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            // of the original contact that we are joining with another.
9052b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            if (writable) {
9062b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                for (int i = 0; i < rawContactIds.length; i++) {
9072b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    c.moveToPosition(i);
9082b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    if (c.getLong(JoinContactQuery.CONTACT_ID) == contactId1) {
9092b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                        int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
9102b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                        if (nameSource == maxDisplayNameSource
9112b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                                && (verifiedNameRawContactId == -1
9122b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                                        || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0)) {
9132b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                            verifiedNameRawContactId = c.getLong(JoinContactQuery._ID);
9142b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                        }
9152b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    }
9162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                }
9172b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            }
9182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } finally {
9192b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            c.close();
9202b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
9212b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9222b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // For each pair of raw contacts, insert an aggregation exception
9232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
9242b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        for (int i = 0; i < rawContactIds.length; i++) {
9252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            for (int j = 0; j < rawContactIds.length; j++) {
9262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                if (i != j) {
9272b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
9282b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                }
9292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            }
9302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
9312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9322b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Mark the original contact as "name verified" to make sure that the contact
9332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // display name does not change as a result of the join
9342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        if (verifiedNameRawContactId != -1) {
9352b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Builder builder = ContentProviderOperation.newUpdate(
9362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId));
9372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            builder.withValue(RawContacts.NAME_VERIFIED, 1);
9382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            operations.add(builder.build());
9392b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
9402b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9412b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        boolean success = false;
9422b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Apply all aggregation exceptions as one batch
9432b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        try {
9442b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            resolver.applyBatch(ContactsContract.AUTHORITY, operations);
945886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            showToast(R.string.contactsJoinedMessage);
9462b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            success = true;
9472b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } catch (RemoteException e) {
9482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Log.e(TAG, "Failed to apply aggregation exception batch", e);
949886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            showToast(R.string.contactSavedErrorToast);
9502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } catch (OperationApplicationException e) {
9512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Log.e(TAG, "Failed to apply aggregation exception batch", e);
952886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            showToast(R.string.contactSavedErrorToast);
9532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
9542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9552b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
9562b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        if (success) {
9572b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Uri uri = RawContacts.getContactLookupUri(resolver,
9582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
9592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            callbackIntent.setData(uri);
9602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
9613a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
9622b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
9632b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9642b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    /**
9652b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation.
9662b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     */
9672b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations,
9682b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            long rawContactId1, long rawContactId2) {
9692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Builder builder =
9702b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
9712b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
9722b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
9732b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
9742b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        operations.add(builder.build());
9752b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
976886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov
977886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    /**
978886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov     * Shows a toast on the UI thread.
979886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov     */
980886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    private void showToast(final int message) {
9813a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler.post(new Runnable() {
982886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov
983886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            @Override
984886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            public void run() {
985886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov                Toast.makeText(ContactSaveService.this, message, Toast.LENGTH_LONG).show();
986886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            }
987886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov        });
988886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    }
9893a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
9903a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    private void deliverCallback(final Intent callbackIntent) {
9913a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler.post(new Runnable() {
9923a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
9933a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            @Override
9943a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            public void run() {
9953a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                deliverCallbackOnUiThread(callbackIntent);
9963a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            }
9973a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        });
9983a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
9993a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
10003a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    void deliverCallbackOnUiThread(final Intent callbackIntent) {
10013a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // TODO: this assumes that if there are multiple instances of the same
10023a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // activity registered, the last one registered is the one waiting for
10033a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // the callback. Validity of this assumption needs to be verified.
1004a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson        for (Listener listener : sListeners) {
1005a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson            if (callbackIntent.getComponent().equals(
1006a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson                    ((Activity) listener).getIntent().getComponent())) {
1007a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson                listener.onServiceCompleted(callbackIntent);
1008a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson                return;
10093a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            }
10103a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        }
10113a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1012173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann}
1013