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
19615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shraunerimport static android.Manifest.permission.WRITE_CONTACTS;
203a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikovimport android.app.Activity;
21173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.app.IntentService;
22173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.content.ContentProviderOperation;
232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.content.ContentProviderOperation.Builder;
24caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentProviderResult;
25caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentResolver;
26e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikovimport android.content.ContentUris;
27caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentValues;
289d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikovimport android.content.Context;
29173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.content.Intent;
302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.content.OperationApplicationException;
312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.database.Cursor;
32caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.net.Uri;
33c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport android.os.Bundle;
34886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikovimport android.os.Handler;
35886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikovimport android.os.Looper;
36a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport android.os.Parcelable;
372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.os.RemoteException;
38173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.provider.ContactsContract;
392b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.provider.ContactsContract.AggregationExceptions;
401ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.GroupMembership;
41548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwellimport android.provider.ContactsContract.CommonDataKinds.StructuredName;
429d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikovimport android.provider.ContactsContract.Contacts;
43caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.Data;
44e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikovimport android.provider.ContactsContract.Groups;
45e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Leeimport android.provider.ContactsContract.PinnedPositions;
46ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelsonimport android.provider.ContactsContract.Profile;
47caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.RawContacts;
48c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoroimport android.provider.ContactsContract.RawContactsEntity;
49173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.util.Log;
502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.widget.Toast;
51173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
52d7ca03e23948c3b2d5f97ec5598d8b50e3fc0b25Chiao Chengimport com.android.contacts.common.database.ContactUpdateUtils;
530d5588da244d0992c3ff8f25d0875fdf95a8c644Chiao Chengimport com.android.contacts.common.model.AccountTypeManager;
54cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactDelta;
55cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactDeltaList;
56cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactModifier;
57428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Chengimport com.android.contacts.common.model.account.AccountWithDataSet;
58615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shraunerimport com.android.contacts.common.util.PermissionsUtil;
593e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jangimport com.android.contacts.editor.ContactEditorFragment;
60637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport com.android.contacts.util.ContactPhotoUtils;
61637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee
62e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Lists;
63e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Sets;
64e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Cheng
65c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.ArrayList;
66c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.HashSet;
67c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.List;
68c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.concurrent.CopyOnWriteArrayList;
69173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
7018ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov/**
7118ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov * A service responsible for saving changes to the content provider.
7218ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov */
73173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannpublic class ContactSaveService extends IntentService {
74173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    private static final String TAG = "ContactSaveService";
75173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
76a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan    /** Set to true in order to view logs on content provider operations */
77a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan    private static final boolean DEBUG = false;
78a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan
79caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String ACTION_NEW_RAW_CONTACT = "newRawContact";
80caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
81caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_ACCOUNT_NAME = "accountName";
82caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_ACCOUNT_TYPE = "accountType";
832b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    public static final String EXTRA_DATA_SET = "dataSet";
84caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_CONTENT_VALUES = "contentValues";
85caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_CALLBACK_INTENT = "callbackIntent";
86caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
87a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String ACTION_SAVE_CONTACT = "saveContact";
88a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String EXTRA_CONTACT_STATE = "state";
89a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String EXTRA_SAVE_MODE = "saveMode";
90ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson    public static final String EXTRA_SAVE_IS_PROFILE = "saveIsProfile";
9136d24d7ede42a252c82c4aa783b2231c5e2eea79Dave Santoro    public static final String EXTRA_SAVE_SUCCEEDED = "saveSucceeded";
92e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    public static final String EXTRA_UPDATED_PHOTOS = "updatedPhotos";
93173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
941ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    public static final String ACTION_CREATE_GROUP = "createGroup";
95e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String ACTION_RENAME_GROUP = "renameGroup";
96e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String ACTION_DELETE_GROUP = "deleteGroup";
972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String ACTION_UPDATE_GROUP = "updateGroup";
98e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String EXTRA_GROUP_ID = "groupId";
99e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String EXTRA_GROUP_LABEL = "groupLabel";
1002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String EXTRA_RAW_CONTACTS_TO_ADD = "rawContactsToAdd";
1012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String EXTRA_RAW_CONTACTS_TO_REMOVE = "rawContactsToRemove";
102e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
1039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String ACTION_SET_STARRED = "setStarred";
1047d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    public static final String ACTION_DELETE_CONTACT = "delete";
105d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    public static final String ACTION_DELETE_MULTIPLE_CONTACTS = "deleteMultipleContacts";
1069d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String EXTRA_CONTACT_URI = "contactUri";
107d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    public static final String EXTRA_CONTACT_IDS = "contactIds";
1089d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String EXTRA_STARRED_FLAG = "starred";
1099d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
1100f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary";
1110f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String ACTION_CLEAR_PRIMARY = "clearPrimary";
1120f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String EXTRA_DATA_ID = "dataId";
1130f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
1142b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String ACTION_JOIN_CONTACTS = "joinContacts";
115d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    public static final String ACTION_JOIN_SEVERAL_CONTACTS = "joinSeveralContacts";
1162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String EXTRA_CONTACT_ID1 = "contactId1";
1172b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String EXTRA_CONTACT_ID2 = "contactId2";
1182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
119683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String ACTION_SET_SEND_TO_VOICEMAIL = "sendToVoicemail";
120683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String EXTRA_SEND_TO_VOICEMAIL_FLAG = "sendToVoicemailFlag";
121683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
122683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String ACTION_SET_RINGTONE = "setRingtone";
123683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String EXTRA_CUSTOM_RINGTONE = "customRingtone";
124683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
125caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet(
126caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.MIMETYPE,
127caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.IS_PRIMARY,
128caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA1,
129caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA2,
130caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA3,
131caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA4,
132caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA5,
133caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA6,
134caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA7,
135caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA8,
136caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA9,
137caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA10,
138caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA11,
139caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA12,
140caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA13,
141caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA14,
142caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA15
143caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    );
144caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
145a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    private static final int PERSIST_TRIES = 3;
146a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
1470653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang    private static final int MAX_CONTACTS_PROVIDER_BATCH_SIZE = 499;
1480653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang
1493a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public interface Listener {
1503a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        public void onServiceCompleted(Intent callbackIntent);
1513a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1523a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
153a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson    private static final CopyOnWriteArrayList<Listener> sListeners =
154a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson            new CopyOnWriteArrayList<Listener>();
1553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1563a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    private Handler mMainHandler;
1573a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
158173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    public ContactSaveService() {
159173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann        super(TAG);
160173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann        setIntentRedelivery(true);
1613a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler = new Handler(Looper.getMainLooper());
1623a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1633a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1643a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public static void registerListener(Listener listener) {
1653a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        if (!(listener instanceof Activity)) {
1663a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            throw new ClassCastException("Only activities can be registered to"
1673a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                    + " receive callback from " + ContactSaveService.class.getName());
1683a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        }
169a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson        sListeners.add(0, listener);
1703a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1713a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1723a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public static void unregisterListener(Listener listener) {
173a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson        sListeners.remove(listener);
174173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    }
175173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
176173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    @Override
177a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public Object getSystemService(String name) {
178a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Object service = super.getSystemService(name);
179a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        if (service != null) {
180a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            return service;
181a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
182a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
183a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return getApplicationContext().getSystemService(name);
184a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
185a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
186a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    @Override
187173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    protected void onHandleIntent(Intent intent) {
1883a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner        if (intent == null) {
1893a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner            Log.d(TAG, "onHandleIntent: could not handle null intent");
1903a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner            return;
1913a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner        }
192615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner        if (!PermissionsUtil.hasPermission(this, WRITE_CONTACTS)) {
193615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner            Log.w(TAG, "No WRITE_CONTACTS permission, unable to write to CP2");
194615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner            // TODO: add more specific error string such as "Turn on Contacts
195615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner            // permission to update your contacts"
196615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner            showToast(R.string.contactSavedErrorToast);
197615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner            return;
198615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner        }
199615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner
2002f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa        // Call an appropriate method. If we're sure it affects how incoming phone calls are
2012f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa        // handled, then notify the fact to in-call screen.
202caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String action = intent.getAction();
203caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        if (ACTION_NEW_RAW_CONTACT.equals(action)) {
204caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            createRawContact(intent);
205a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        } else if (ACTION_SAVE_CONTACT.equals(action)) {
206a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            saveContact(intent);
2071ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        } else if (ACTION_CREATE_GROUP.equals(action)) {
2081ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov            createGroup(intent);
209e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        } else if (ACTION_RENAME_GROUP.equals(action)) {
210e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            renameGroup(intent);
211e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        } else if (ACTION_DELETE_GROUP.equals(action)) {
212e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            deleteGroup(intent);
2132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        } else if (ACTION_UPDATE_GROUP.equals(action)) {
2142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            updateGroup(intent);
2159d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        } else if (ACTION_SET_STARRED.equals(action)) {
2169d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            setStarred(intent);
2170f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) {
2180f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            setSuperPrimary(intent);
2190f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        } else if (ACTION_CLEAR_PRIMARY.equals(action)) {
2200f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            clearPrimary(intent);
221d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        } else if (ACTION_DELETE_MULTIPLE_CONTACTS.equals(action)) {
222d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell            deleteMultipleContacts(intent);
2237d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        } else if (ACTION_DELETE_CONTACT.equals(action)) {
2247d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            deleteContact(intent);
2252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } else if (ACTION_JOIN_CONTACTS.equals(action)) {
2262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            joinContacts(intent);
227d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        } else if (ACTION_JOIN_SEVERAL_CONTACTS.equals(action)) {
228d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            joinSeveralContacts(intent);
229683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) {
230683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            setSendToVoicemail(intent);
231683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        } else if (ACTION_SET_RINGTONE.equals(action)) {
232683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            setRingtone(intent);
233caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
234caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
235caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
2369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    /**
2379d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to create a new raw contact
2389d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * using data presented as a set of ContentValues.
2399d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     */
2409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createNewRawContactIntent(Context context,
2412b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            ArrayList<ContentValues> values, AccountWithDataSet account,
242e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction) {
2439d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(
2449d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov                context, ContactSaveService.class);
2459d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT);
2469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        if (account != null) {
2479d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
2489d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
2492b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet);
2509d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        }
2519d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putParcelableArrayListExtra(
2529d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov                ContactSaveService.EXTRA_CONTENT_VALUES, values);
2539d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
2549d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // Callback intent will be invoked by the service once the new contact is
2559d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // created.  The service will put the URI of the new contact as "data" on
2569d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // the callback intent.
2579d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
2589d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        callbackIntent.setAction(callbackAction);
2599d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
2609d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        return serviceIntent;
2619d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    }
2629d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
263caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    private void createRawContact(Intent intent) {
264caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
265caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
2662b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String dataSet = intent.getStringExtra(EXTRA_DATA_SET);
267caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES);
268caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
269caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
270caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
271caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
272caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .withValue(RawContacts.ACCOUNT_NAME, accountName)
273caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .withValue(RawContacts.ACCOUNT_TYPE, accountType)
2742b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                .withValue(RawContacts.DATA_SET, dataSet)
275caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .build());
276caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
277caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        int size = valueList.size();
278caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        for (int i = 0; i < size; i++) {
279caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            ContentValues values = valueList.get(i);
280caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            values.keySet().retainAll(ALLOWED_DATA_COLUMNS);
281caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
282caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .withValueBackReference(Data.RAW_CONTACT_ID, 0)
283caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .withValues(values)
284caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .build());
285caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
286caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
287caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ContentResolver resolver = getContentResolver();
288caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ContentProviderResult[] results;
289caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        try {
290caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            results = resolver.applyBatch(ContactsContract.AUTHORITY, operations);
291caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        } catch (Exception e) {
292caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            throw new RuntimeException("Failed to store new contact", e);
293caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
294caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
295caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Uri rawContactUri = results[0].uri;
296caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri));
297caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
2983a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
299caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
300caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
301caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    /**
302a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     * Creates an intent that can be sent to this service to create a new raw contact
303a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     * using data presented as a set of ContentValues.
304e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * This variant is more convenient to use when there is only one photo that can
305e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * possibly be updated, as in the Contact Details screen.
306e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * @param rawContactId identifies a writable raw-contact whose photo is to be updated.
307e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * @param updatedPhotoPath denotes a temporary file containing the contact's new photo.
308a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     */
309851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu    public static Intent createSaveContactIntent(Context context, RawContactDeltaList state,
310e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            String saveModeExtraKey, int saveMode, boolean isProfile,
311e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction, long rawContactId,
312637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            Uri updatedPhotoPath) {
313e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        Bundle bundle = new Bundle();
314637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        bundle.putParcelable(String.valueOf(rawContactId), updatedPhotoPath);
315e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        return createSaveContactIntent(context, state, saveModeExtraKey, saveMode, isProfile,
3163e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang                callbackActivity, callbackAction, bundle, /* backPressed =*/ false);
317e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    }
318e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
319e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    /**
320e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * Creates an intent that can be sent to this service to create a new raw contact
321e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * using data presented as a set of ContentValues.
322e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * This variant is used when multiple contacts' photos may be updated, as in the
323e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * Contact Editor.
324e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * @param updatedPhotos maps each raw-contact's ID to the file-path of the new photo.
3253e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang     * @param backPressed whether the save was initiated as a result of a back button press
3263e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang     *         or because the framework stopped the editor Activity
327e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     */
328851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu    public static Intent createSaveContactIntent(Context context, RawContactDeltaList state,
329e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            String saveModeExtraKey, int saveMode, boolean isProfile,
330e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction,
3313e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang            Bundle updatedPhotos, boolean backPressed) {
332a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Intent serviceIntent = new Intent(
333a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                context, ContactSaveService.class);
334a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT);
335a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state);
336ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson        serviceIntent.putExtra(EXTRA_SAVE_IS_PROFILE, isProfile);
337e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        if (updatedPhotos != null) {
338e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus            serviceIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos);
339e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        }
340a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
341e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        if (callbackActivity != null) {
342e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            // Callback intent will be invoked by the service once the contact is
343e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            // saved.  The service will put the URI of the new contact as "data" on
344e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            // the callback intent.
345e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Intent callbackIntent = new Intent(context, callbackActivity);
346e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            callbackIntent.putExtra(saveModeExtraKey, saveMode);
347e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            callbackIntent.setAction(callbackAction);
3481e8801bc9bc60bdd1c95f582c460590272cfad64Walter Jang            if (updatedPhotos != null) {
3491e8801bc9bc60bdd1c95f582c460590272cfad64Walter Jang                callbackIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos);
3501e8801bc9bc60bdd1c95f582c460590272cfad64Walter Jang            }
3513e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang            callbackIntent.putExtra(ContactEditorFragment.INTENT_EXTRA_SAVE_BACK_PRESSED,
3523e76408e47ca135c092b5eee73ae49d8697b0a10Walter Jang                    backPressed);
353e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
354e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        }
355a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return serviceIntent;
356a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
357a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
358a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    private void saveContact(Intent intent) {
359851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu        RawContactDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE);
360ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson        boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false);
361e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        Bundle updatedPhotos = intent.getParcelableExtra(EXTRA_UPDATED_PHOTOS);
362a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
3630809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner        if (state == null) {
3640809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner            Log.e(TAG, "Invalid arguments for saveContact request");
3650809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner            return;
3660809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner        }
3670809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner
368a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // Trim any empty fields, and RawContacts, before persisting
369a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
370851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu        RawContactModifier.trimEmpty(state, accountTypes);
371a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
372a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Uri lookupUri = null;
373a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
374a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final ContentResolver resolver = getContentResolver();
375e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        boolean succeeded = false;
376a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
377ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        // Keep track of the id of a newly raw-contact (if any... there can be at most one).
378ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        long insertedRawContactId = -1;
379ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus
380a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // Attempt to persist changes
381a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        int tries = 0;
382a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        while (tries++ < PERSIST_TRIES) {
383a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            try {
384a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Build operations and try applying
385a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final ArrayList<ContentProviderOperation> diff = state.buildDiff();
386a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                if (DEBUG) {
387a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    Log.v(TAG, "Content Provider Operations:");
388a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    for (ContentProviderOperation operation : diff) {
389a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                        Log.v(TAG, operation.toString());
390a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    }
391a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                }
392a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan
393a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                ContentProviderResult[] results = null;
394a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (!diff.isEmpty()) {
395a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
396511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                    if (results == null) {
397511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                        Log.w(TAG, "Resolver.applyBatch failed in saveContacts");
398511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                        // Retry save
399511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                        continue;
400511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                    }
401a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
402a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
403a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final long rawContactId = getRawContactId(state, diff, results);
404a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (rawContactId == -1) {
405a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    throw new IllegalStateException("Could not determine RawContact ID after save");
406a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
407ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                // We don't have to check to see if the value is still -1.  If we reach here,
408ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                // the previous loop iteration didn't succeed, so any ID that we obtained is bogus.
409ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                insertedRawContactId = getInsertedRawContactId(diff, results);
4107c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                if (isProfile) {
4117c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    // Since the profile supports local raw contacts, which may have been completely
4127c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    // removed if all information was removed, we need to do a special query to
4137c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    // get the lookup URI for the profile contact (if it still exists).
4147c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    Cursor c = resolver.query(Profile.CONTENT_URI,
4157c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                            new String[] {Contacts._ID, Contacts.LOOKUP_KEY},
4167c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                            null, null, null);
417e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner                    if (c == null) {
418e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner                        continue;
419e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner                    }
4207c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    try {
421162b7e34fb581d0fa279957af5136d190c40759fErik                        if (c.moveToFirst()) {
422162b7e34fb581d0fa279957af5136d190c40759fErik                            final long contactId = c.getLong(0);
423162b7e34fb581d0fa279957af5136d190c40759fErik                            final String lookupKey = c.getString(1);
424162b7e34fb581d0fa279957af5136d190c40759fErik                            lookupUri = Contacts.getLookupUri(contactId, lookupKey);
425162b7e34fb581d0fa279957af5136d190c40759fErik                        }
4267c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    } finally {
4277c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                        c.close();
4287c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    }
4297c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                } else {
4307c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
4317c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                                    rawContactId);
4327c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri);
4337c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                }
434e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner                if (lookupUri != null) {
435e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner                    Log.v(TAG, "Saved contact. New URI: " + lookupUri);
436e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner                }
437e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
438e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                // We can change this back to false later, if we fail to save the contact photo.
439e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                succeeded = true;
440a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                break;
441a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
442a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            } catch (RemoteException e) {
443a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Something went wrong, bail without success
444a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                Log.e(TAG, "Problem persisting user edits", e);
445a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                break;
446a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
44757fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner            } catch (IllegalArgumentException e) {
44857fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner                // This is thrown by applyBatch on malformed requests
44957fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner                Log.e(TAG, "Problem persisting user edits", e);
45057fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner                showToast(R.string.contactSavedErrorToast);
45157fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner                break;
45257fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner
453a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            } catch (OperationApplicationException e) {
454a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Version consistency failed, re-parent change and try again
455a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString());
456a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN(");
457a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                boolean first = true;
458a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final int count = state.size();
459a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                for (int i = 0; i < count; i++) {
460a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    Long rawContactId = state.getRawContactId(i);
461a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    if (rawContactId != null && rawContactId != -1) {
462a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        if (!first) {
463a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                            sb.append(',');
464a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        }
465a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        sb.append(rawContactId);
466a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        first = false;
467a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    }
468a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
469a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                sb.append(")");
470a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
471a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (first) {
4723b6c628fc9e717a41c2954b1101c3a04ad382c55Brian Attwell                    throw new IllegalStateException(
4733b6c628fc9e717a41c2954b1101c3a04ad382c55Brian Attwell                            "Version consistency failed for a new contact", e);
474a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
475a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
476851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu                final RawContactDeltaList newState = RawContactDeltaList.fromQuery(
477c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                        isProfile
478c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                                ? RawContactsEntity.PROFILE_CONTENT_URI
479c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                                : RawContactsEntity.CONTENT_URI,
480c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                        resolver, sb.toString(), null, null);
481851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu                state = RawContactDeltaList.mergeAfter(newState, state);
482c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro
483c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                // Update the new state to use profile URIs if appropriate.
484c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                if (isProfile) {
485851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu                    for (RawContactDelta delta : state) {
486c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                        delta.setProfileQueryUri();
487c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                    }
488c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                }
489a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            }
490a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
491a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
492e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        // Now save any updated photos.  We do this at the end to ensure that
493e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        // the ContactProvider already knows about newly-created contacts.
494e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        if (updatedPhotos != null) {
495e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus            for (String key : updatedPhotos.keySet()) {
496637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee                Uri photoUri = updatedPhotos.getParcelable(key);
497e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                long rawContactId = Long.parseLong(key);
498ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus
499ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                // If the raw-contact ID is negative, we are saving a new raw-contact;
500ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                // replace the bogus ID with the new one that we actually saved the contact at.
501ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                if (rawContactId < 0) {
502ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                    rawContactId = insertedRawContactId;
503ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                }
504ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus
505511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                // If the save failed, insertedRawContactId will be -1
506511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                if (rawContactId < 0 || !saveUpdatedPhoto(rawContactId, photoUri)) {
507511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                    succeeded = false;
508511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                }
509e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus            }
510e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        }
511e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
512e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
513e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        if (callbackIntent != null) {
514e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            if (succeeded) {
515e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus                // Mark the intent to indicate that the save was successful (even if the lookup URI
516e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus                // is now null).  For local contacts or the local profile, it's possible that the
517e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus                // save triggered removal of the contact, so no lookup URI would exist..
518e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus                callbackIntent.putExtra(EXTRA_SAVE_SUCCEEDED, true);
519e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            }
520e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            callbackIntent.setData(lookupUri);
521e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            deliverCallback(callbackIntent);
522e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        }
523a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
524a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
525e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    /**
526e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * Save updated photo for the specified raw-contact.
527e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * @return true for success, false for failure
528e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     */
529637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    private boolean saveUpdatedPhoto(long rawContactId, Uri photoUri) {
530ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        final Uri outputUri = Uri.withAppendedPath(
531e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
532e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                RawContacts.DisplayPhoto.CONTENT_DIRECTORY);
533e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
534637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        return ContactPhotoUtils.savePhotoFromUriToUri(this, photoUri, outputUri, true);
535e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    }
536e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
537ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus    /**
538ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus     * Find the ID of an existing or newly-inserted raw-contact.  If none exists, return -1.
539ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus     */
540851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu    private long getRawContactId(RawContactDeltaList state,
541a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            final ArrayList<ContentProviderOperation> diff,
542a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            final ContentProviderResult[] results) {
543ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        long existingRawContactId = state.findRawContactId();
544ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        if (existingRawContactId != -1) {
545ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus            return existingRawContactId;
546a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
547a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
548ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        return getInsertedRawContactId(diff, results);
549ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus    }
550ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus
551ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus    /**
552ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus     * Find the ID of a newly-inserted raw-contact.  If none exists, return -1.
553ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus     */
554ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus    private long getInsertedRawContactId(
555ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus            final ArrayList<ContentProviderOperation> diff,
556ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus            final ContentProviderResult[] results) {
557568f4e72711908455ccd20fbb04c1017b10d7e1cJay Shrauner        if (results == null) {
558568f4e72711908455ccd20fbb04c1017b10d7e1cJay Shrauner            return -1;
559568f4e72711908455ccd20fbb04c1017b10d7e1cJay Shrauner        }
560a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final int diffSize = diff.size();
5613d7edc3ef4f2521639bae91c93a8238e5a9509c1Jay Shrauner        final int numResults = results.length;
5623d7edc3ef4f2521639bae91c93a8238e5a9509c1Jay Shrauner        for (int i = 0; i < diffSize && i < numResults; i++) {
563a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            ContentProviderOperation operation = diff.get(i);
56413f94e1c52f553a7137e6730c09c4d28c3f54c9fBrian Attwell            if (operation.isInsert() && operation.getUri().getEncodedPath().contains(
565a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                            RawContacts.CONTENT_URI.getEncodedPath())) {
566a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                return ContentUris.parseId(results[i].uri);
567a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            }
568a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
569a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return -1;
570a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
571a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
572a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    /**
573717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * Creates an intent that can be sent to this service to create a new group as
574717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * well as add new members at the same time.
575717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     *
576717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param context of the application
577717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param account in which the group should be created
578717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param label is the name of the group (cannot be null)
579717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param rawContactsToAdd is an array of raw contact IDs for contacts that
580717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     *            should be added to the group
581717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param callbackActivity is the activity to send the callback intent to
582717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param callbackAction is the intent action for the callback intent
583caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov     */
5842b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    public static Intent createNewGroupIntent(Context context, AccountWithDataSet account,
585e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            String label, long[] rawContactsToAdd, Class<? extends Activity> callbackActivity,
586717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            String callbackAction) {
5879d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
5889d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP);
5899d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
5909d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
5912b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet);
5929d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label);
593717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
594caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
5959d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // Callback intent will be invoked by the service once the new group is
596717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // created.
5979d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
5989d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        callbackIntent.setAction(callbackAction);
5991ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
6009d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
6011ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        return serviceIntent;
6021ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    }
6031ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
6041ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    private void createGroup(Intent intent) {
6052b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
6062b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
6072b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String dataSet = intent.getStringExtra(EXTRA_DATA_SET);
6082b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
609717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
6101ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
6111ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        ContentValues values = new ContentValues();
6121ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.ACCOUNT_TYPE, accountType);
6131ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.ACCOUNT_NAME, accountName);
6142b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        values.put(Groups.DATA_SET, dataSet);
6151ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.TITLE, label);
6161ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
617717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final ContentResolver resolver = getContentResolver();
618717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
619717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Create the new group
620717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values);
621717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
622717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // If there's no URI, then the insertion failed. Abort early because group members can't be
623717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // added if the group doesn't exist
6241ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        if (groupUri == null) {
625717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            Log.e(TAG, "Couldn't create group with label " + label);
6261ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov            return;
6271ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        }
6281ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
629717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Add new group members
630717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri));
631717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
632717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // TODO: Move this into the contact editor where it belongs. This needs to be integrated
633717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // with the way other intent extras that are passed to the {@link ContactEditorActivity}.
6341ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.clear();
6351ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
6361ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri));
6371ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
6381ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
639c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setData(groupUri);
640717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // TODO: This can be taken out when the above TODO is addressed
6411ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values));
6423a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
6431ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    }
6441ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
6451ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    /**
6469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to rename a group.
6471ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov     */
648c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan    public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel,
649e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction) {
6509d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
6519d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP);
6529d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
6539d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
654c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
655c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        // Callback intent will be invoked by the service once the group is renamed.
656c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        Intent callbackIntent = new Intent(context, callbackActivity);
657c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setAction(callbackAction);
658c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
659c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
660caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        return serviceIntent;
661caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
662e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
663e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    private void renameGroup(Intent intent) {
664e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
665e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
666e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
667e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        if (groupId == -1) {
668e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            Log.e(TAG, "Invalid arguments for renameGroup request");
669e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            return;
670e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        }
671e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
672e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        ContentValues values = new ContentValues();
673e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        values.put(Groups.TITLE, label);
674c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
675c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        getContentResolver().update(groupUri, values, null, null);
676c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
677c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
678c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setData(groupUri);
679c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        deliverCallback(callbackIntent);
680e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
681e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
682e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    /**
6839d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to delete a group.
684e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov     */
6859d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createGroupDeletionIntent(Context context, long groupId) {
6869d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
6879d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP);
688e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
689e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        return serviceIntent;
690e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
691e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
692e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    private void deleteGroup(Intent intent) {
693e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
694e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        if (groupId == -1) {
695e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            Log.e(TAG, "Invalid arguments for deleteGroup request");
696e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            return;
697e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        }
698e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
699e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        getContentResolver().delete(
700e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov                ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null);
701e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
702e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
703e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    /**
7042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * Creates an intent that can be sent to this service to rename a group as
7052d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * well as add and remove members from the group.
7062d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *
7072d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param context of the application
7082d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param groupId of the group that should be modified
7092d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param newLabel is the updated name of the group (can be null if the name
7102d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            should not be updated)
7112d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param rawContactsToAdd is an array of raw contact IDs for contacts that
7122d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            should be added to the group
7132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param rawContactsToRemove is an array of raw contact IDs for contacts
7142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            that should be removed from the group
7152d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param callbackActivity is the activity to send the callback intent to
7162d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param callbackAction is the intent action for the callback intent
7172d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     */
7182d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel,
7192d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            long[] rawContactsToAdd, long[] rawContactsToRemove,
720e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction) {
7212d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        Intent serviceIntent = new Intent(context, ContactSaveService.class);
7222d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP);
7232d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
7242d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
7252d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
7262d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_REMOVE,
7272d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactsToRemove);
7282d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7292d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        // Callback intent will be invoked by the service once the group is updated
7302d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        Intent callbackIntent = new Intent(context, callbackActivity);
7312d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        callbackIntent.setAction(callbackAction);
7322d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
7332d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        return serviceIntent;
7352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    }
7362d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7372d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    private void updateGroup(Intent intent) {
7382d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
7392d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
7402d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
7412d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long[] rawContactsToRemove = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_REMOVE);
7422d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7432d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        if (groupId == -1) {
7442d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            Log.e(TAG, "Invalid arguments for updateGroup request");
7452d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            return;
7462d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
7472d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7482d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        final ContentResolver resolver = getContentResolver();
7492d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
7502d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7512d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        // Update group name if necessary
7522d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        if (label != null) {
7532d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            ContentValues values = new ContentValues();
7542d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            values.put(Groups.TITLE, label);
755717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            resolver.update(groupUri, values, null, null);
7562d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
7572d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
758717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Add and remove members if necessary
759717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        addMembersToGroup(resolver, rawContactsToAdd, groupId);
760717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        removeMembersFromGroup(resolver, rawContactsToRemove, groupId);
761717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
762717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
763717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        callbackIntent.setData(groupUri);
764717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        deliverCallback(callbackIntent);
765717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    }
766717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
76718958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann    private static void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd,
768717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            long groupId) {
769717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        if (rawContactsToAdd == null) {
770717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            return;
771717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        }
7722d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        for (long rawContactId : rawContactsToAdd) {
7732d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            try {
7742d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ArrayList<ContentProviderOperation> rawContactOperations =
7752d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        new ArrayList<ContentProviderOperation>();
7762d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7772d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Build an assert operation to ensure the contact is not already in the group
7782d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ContentProviderOperation.Builder assertBuilder = ContentProviderOperation
7792d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        .newAssertQuery(Data.CONTENT_URI);
7802d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                assertBuilder.withSelection(Data.RAW_CONTACT_ID + "=? AND " +
7812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
7822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        new String[] { String.valueOf(rawContactId),
7832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
7842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                assertBuilder.withExpectedCount(0);
7852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactOperations.add(assertBuilder.build());
7862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7872d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Build an insert operation to add the contact to the group
7882d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ContentProviderOperation.Builder insertBuilder = ContentProviderOperation
7892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        .newInsert(Data.CONTENT_URI);
7902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(Data.RAW_CONTACT_ID, rawContactId);
7912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
7922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(GroupMembership.GROUP_ROW_ID, groupId);
7932d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactOperations.add(insertBuilder.build());
7942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
7952d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                if (DEBUG) {
7962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    for (ContentProviderOperation operation : rawContactOperations) {
7972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        Log.v(TAG, operation.toString());
7982d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    }
7992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                }
8002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
8012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Apply batch
8022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                if (!rawContactOperations.isEmpty()) {
80318958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann                    resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations);
8042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                }
8052d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            } catch (RemoteException e) {
8062d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Something went wrong, bail without success
8072d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                Log.e(TAG, "Problem persisting user edits for raw contact ID " +
8082d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(rawContactId), e);
8092d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            } catch (OperationApplicationException e) {
8102d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // The assert could have failed because the contact is already in the group,
8112d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // just continue to the next contact
8122d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                Log.w(TAG, "Assert failed in adding raw contact ID " +
8132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(rawContactId) + ". Already exists in group " +
8142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(groupId), e);
8152d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            }
8162d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
817717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    }
8182d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
81918958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann    private static void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove,
820717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            long groupId) {
821717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        if (rawContactsToRemove == null) {
822717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            return;
823717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        }
8242d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        for (long rawContactId : rawContactsToRemove) {
8252d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // Apply the delete operation on the data row for the given raw contact's
8262d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // membership in the given group. If no contact matches the provided selection, then
8272d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // nothing will be done. Just continue to the next contact.
82818958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann            resolver.delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " +
8292d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
8302d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    new String[] { String.valueOf(rawContactId),
8312d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
8322d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
8332d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    }
8342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
8352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    /**
8369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to star or un-star a contact.
837e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov     */
8389d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) {
8399d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
8409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED);
8419d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
8429d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value);
8439d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
844e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        return serviceIntent;
845e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
8469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
8479d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    private void setStarred(Intent intent) {
8489d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
8499d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false);
8509d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        if (contactUri == null) {
8519d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            Log.e(TAG, "Invalid arguments for setStarred request");
8529d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            return;
8539d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        }
8549d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
8559d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        final ContentValues values = new ContentValues(1);
8569d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        values.put(Contacts.STARRED, value);
8579d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        getContentResolver().update(contactUri, values, null, null);
858e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee
859e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        // Undemote the contact if necessary
860e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        final Cursor c = getContentResolver().query(contactUri, new String[] {Contacts._ID},
861e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee                null, null, null);
862c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner        if (c == null) {
863c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner            return;
864c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner        }
865e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        try {
866e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee            if (c.moveToFirst()) {
867e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee                final long id = c.getLong(0);
868bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee
869bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee                // Don't bother undemoting if this contact is the user's profile.
870bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee                if (id < Profile.MIN_ID) {
8712d88efaf9efa059c70783acffb6ec3055e1b883bBrian Attwell                    PinnedPositions.undemote(getContentResolver(), id);
872bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee                }
873e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee            }
874e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        } finally {
875e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee            c.close();
876e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        }
8779d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    }
8780f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
8790f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    /**
880683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     * Creates an intent that can be sent to this service to set the redirect to voicemail.
881683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     */
882683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static Intent createSetSendToVoicemail(Context context, Uri contactUri,
883683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            boolean value) {
884683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Intent serviceIntent = new Intent(context, ContactSaveService.class);
885683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.setAction(ContactSaveService.ACTION_SET_SEND_TO_VOICEMAIL);
886683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
887683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_SEND_TO_VOICEMAIL_FLAG, value);
888683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
889683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        return serviceIntent;
890683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
891683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
892683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    private void setSendToVoicemail(Intent intent) {
893683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
894683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        boolean value = intent.getBooleanExtra(EXTRA_SEND_TO_VOICEMAIL_FLAG, false);
895683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        if (contactUri == null) {
896683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            Log.e(TAG, "Invalid arguments for setRedirectToVoicemail");
897683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            return;
898683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        }
899683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
900683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        final ContentValues values = new ContentValues(1);
901683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        values.put(Contacts.SEND_TO_VOICEMAIL, value);
902683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        getContentResolver().update(contactUri, values, null, null);
903683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
904683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
905683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    /**
906683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     * Creates an intent that can be sent to this service to save the contact's ringtone.
907683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     */
908683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static Intent createSetRingtone(Context context, Uri contactUri,
909683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            String value) {
910683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Intent serviceIntent = new Intent(context, ContactSaveService.class);
911683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.setAction(ContactSaveService.ACTION_SET_RINGTONE);
912683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
913683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CUSTOM_RINGTONE, value);
914683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
915683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        return serviceIntent;
916683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
917683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
918683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    private void setRingtone(Intent intent) {
919683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
920683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        String value = intent.getStringExtra(EXTRA_CUSTOM_RINGTONE);
921683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        if (contactUri == null) {
922683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            Log.e(TAG, "Invalid arguments for setRingtone");
923683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            return;
924683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        }
925683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        ContentValues values = new ContentValues(1);
926683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        values.put(Contacts.CUSTOM_RINGTONE, value);
927683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        getContentResolver().update(contactUri, values, null, null);
928683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
929683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
930683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    /**
9310f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * Creates an intent that sets the selected data item as super primary (default)
9320f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     */
9330f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static Intent createSetSuperPrimaryIntent(Context context, long dataId) {
9340f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        Intent serviceIntent = new Intent(context, ContactSaveService.class);
9350f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY);
9360f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
9370f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        return serviceIntent;
9380f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
9390f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
9400f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    private void setSuperPrimary(Intent intent) {
9410f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
9420f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        if (dataId == -1) {
9430f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            Log.e(TAG, "Invalid arguments for setSuperPrimary request");
9440f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            return;
9450f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        }
9460f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
947d7ca03e23948c3b2d5f97ec5598d8b50e3fc0b25Chiao Cheng        ContactUpdateUtils.setSuperPrimary(this, dataId);
9480f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
9490f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
9500f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    /**
9510f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * Creates an intent that clears the primary flag of all data items that belong to the same
9520f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * raw_contact as the given data item. Will only clear, if the data item was primary before
9530f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * this call
9540f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     */
9550f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static Intent createClearPrimaryIntent(Context context, long dataId) {
9560f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        Intent serviceIntent = new Intent(context, ContactSaveService.class);
9570f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY);
9580f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
9590f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        return serviceIntent;
9600f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
9610f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
9620f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    private void clearPrimary(Intent intent) {
9630f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
9640f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        if (dataId == -1) {
9650f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            Log.e(TAG, "Invalid arguments for clearPrimary request");
9660f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            return;
9670f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        }
9680f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
9690f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        // Update the primary values in the data record.
9700f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        ContentValues values = new ContentValues(1);
9710f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_SUPER_PRIMARY, 0);
9720f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_PRIMARY, 0);
9730f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
9740f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
9750f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann                values, null, null);
9760f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
9777d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
9787d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    /**
9797d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov     * Creates an intent that can be sent to this service to delete a contact.
9807d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov     */
9817d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    public static Intent createDeleteContactIntent(Context context, Uri contactUri) {
9827d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
9837d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT);
9847d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
9857d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        return serviceIntent;
9867d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    }
9877d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
988d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    /**
989d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell     * Creates an intent that can be sent to this service to delete multiple contacts.
990d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell     */
991d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    public static Intent createDeleteMultipleContactsIntent(Context context,
992d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell            long[] contactIds) {
993d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        Intent serviceIntent = new Intent(context, ContactSaveService.class);
994d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        serviceIntent.setAction(ContactSaveService.ACTION_DELETE_MULTIPLE_CONTACTS);
995d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_IDS, contactIds);
996d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        return serviceIntent;
997d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    }
998d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell
9997d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    private void deleteContact(Intent intent) {
10007d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
10017d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        if (contactUri == null) {
10027d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            Log.e(TAG, "Invalid arguments for deleteContact request");
10037d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            return;
10047d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        }
10057d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
10067d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        getContentResolver().delete(contactUri, null, null);
10077d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    }
10082b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1009d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    private void deleteMultipleContacts(Intent intent) {
1010d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        final long[] contactIds = intent.getLongArrayExtra(EXTRA_CONTACT_IDS);
1011d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        if (contactIds == null) {
1012d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell            Log.e(TAG, "Invalid arguments for deleteMultipleContacts request");
1013d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell            return;
1014d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        }
1015d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        for (long contactId : contactIds) {
1016d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell            final Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
1017d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell            getContentResolver().delete(contactUri, null, null);
1018d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        }
1019e986c6b5954f7b9fea58cfb11c86b61d3defa271Brian Attwell        showToast(R.string.contacts_deleted_toast);
1020d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    }
1021d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell
10222b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    /**
10232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     * Creates an intent that can be sent to this service to join two contacts.
1024d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell     * The resulting contact uses the name from {@param contactId1} if possible.
10252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     */
10262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static Intent createJoinContactsIntent(Context context, long contactId1,
1027d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            long contactId2, Class<? extends Activity> callbackActivity, String callbackAction) {
10282b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
10292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS);
10302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1);
10312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2);
10322b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
10332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Callback intent will be invoked by the service once the contacts are joined.
10342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
10352b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        callbackIntent.setAction(callbackAction);
10362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
10372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
10382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        return serviceIntent;
10392b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
10402b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1041d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    /**
1042d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell     * Creates an intent to join all raw contacts inside {@param contactIds}'s contacts.
1043d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell     * No special attention is paid to where the resulting contact's name is taken from.
1044d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell     */
1045d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    public static Intent createJoinSeveralContactsIntent(Context context, long[] contactIds) {
1046d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        Intent serviceIntent = new Intent(context, ContactSaveService.class);
1047d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        serviceIntent.setAction(ContactSaveService.ACTION_JOIN_SEVERAL_CONTACTS);
1048d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_IDS, contactIds);
1049d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        return serviceIntent;
1050d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    }
1051d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
10522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
10532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    private interface JoinContactQuery {
10542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        String[] PROJECTION = {
10552b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts._ID,
10562b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts.CONTACT_ID,
10572b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts.DISPLAY_NAME_SOURCE,
10582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        };
10592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
10602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int _ID = 0;
10612b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int CONTACT_ID = 1;
1062548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        int DISPLAY_NAME_SOURCE = 2;
1063548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell    }
1064548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell
1065548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell    private interface ContactEntityQuery {
1066548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        String[] PROJECTION = {
1067548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                Contacts.Entity.DATA_ID,
1068548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                Contacts.Entity.CONTACT_ID,
1069548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                Contacts.Entity.IS_SUPER_PRIMARY,
1070548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        };
1071548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        String SELECTION = Data.MIMETYPE + " = '" + StructuredName.CONTENT_ITEM_TYPE + "'" +
1072548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                " AND " + StructuredName.DISPLAY_NAME + "=" + Contacts.DISPLAY_NAME +
1073548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                " AND " + StructuredName.DISPLAY_NAME + " IS NOT NULL " +
1074548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                " AND " + StructuredName.DISPLAY_NAME + " != '' ";
1075548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell
1076548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        int DATA_ID = 0;
1077548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        int CONTACT_ID = 1;
1078548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        int IS_SUPER_PRIMARY = 2;
10792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
10802b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1081d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    private void joinSeveralContacts(Intent intent) {
1082d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        final long[] contactIds = intent.getLongArrayExtra(EXTRA_CONTACT_IDS);
1083548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell
1084d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        // Load raw contact IDs for all contacts involved.
1085d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        long rawContactIds[] = getRawContactIdsForAggregation(contactIds);
1086d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        if (rawContactIds == null) {
1087d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            Log.e(TAG, "Invalid arguments for joinSeveralContacts request");
10882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            return;
10892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
10902b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1091d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        // For each pair of raw contacts, insert an aggregation exception
10922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        final ContentResolver resolver = getContentResolver();
10930653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang        // The maximum number of operations per batch (aka yield point) is 500. See b/22480225
10940653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang        final int batchSize = MAX_CONTACTS_PROVIDER_BATCH_SIZE;
10950653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang        final ArrayList<ContentProviderOperation> operations = new ArrayList<>(batchSize);
1096d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        for (int i = 0; i < rawContactIds.length; i++) {
1097d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            for (int j = 0; j < rawContactIds.length; j++) {
1098d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                if (i != j) {
1099d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                    buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
1100d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                }
11010653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                // Before we get to 500 we need to flush the operations list
11020653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                if (operations.size() > 0 && operations.size() % batchSize == 0) {
11030653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                    if (!applyJoinOperations(resolver, operations)) {
11040653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                        return;
11050653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                    }
11060653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                    operations.clear();
11070653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                }
1108d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            }
1109d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        }
11100653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang        if (operations.size() > 0 && !applyJoinOperations(resolver, operations)) {
11110653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang            return;
11120653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang        }
11130653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang        showToast(R.string.contactsJoinedMessage);
11140653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang    }
1115d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
11160653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang    /** Returns true if the batch was successfully applied and false otherwise. */
11170653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang    private boolean applyJoinOperations(ContentResolver resolver,
11180653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang            ArrayList<ContentProviderOperation> operations) {
1119d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        try {
1120d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            resolver.applyBatch(ContactsContract.AUTHORITY, operations);
11210653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang            return true;
1122d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        } catch (RemoteException | OperationApplicationException e) {
1123d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            Log.e(TAG, "Failed to apply aggregation exception batch", e);
1124d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            showToast(R.string.contactSavedErrorToast);
11250653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang            return false;
1126d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        }
1127d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    }
1128d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
1129d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
1130d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    private void joinContacts(Intent intent) {
1131d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1);
1132d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1);
11332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
11342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Load raw contact IDs for all raw contacts involved - currently edited and selected
1135548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        // in the join UIs.
1136548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        long rawContactIds[] = getRawContactIdsForAggregation(contactId1, contactId2);
1137548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        if (rawContactIds == null) {
1138d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            Log.e(TAG, "Invalid arguments for joinContacts request");
1139c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner            return;
1140c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner        }
11412b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1142548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
11432b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
11442b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // For each pair of raw contacts, insert an aggregation exception
11452b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        for (int i = 0; i < rawContactIds.length; i++) {
11462b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            for (int j = 0; j < rawContactIds.length; j++) {
11472b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                if (i != j) {
11482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
11492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                }
11502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            }
11512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
11522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1153d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        final ContentResolver resolver = getContentResolver();
1154d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
1155548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        // Use the name for contactId1 as the name for the newly aggregated contact.
1156548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        final Uri contactId1Uri = ContentUris.withAppendedId(
1157548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                Contacts.CONTENT_URI, contactId1);
1158548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        final Uri entityUri = Uri.withAppendedPath(
1159548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                contactId1Uri, Contacts.Entity.CONTENT_DIRECTORY);
1160548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        Cursor c = resolver.query(entityUri,
1161548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                ContactEntityQuery.PROJECTION, ContactEntityQuery.SELECTION, null, null);
1162548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        if (c == null) {
1163548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            Log.e(TAG, "Unable to open Contacts DB cursor");
1164548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            showToast(R.string.contactSavedErrorToast);
1165548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            return;
1166548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        }
1167548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        long dataIdToAddSuperPrimary = -1;
1168548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        try {
1169548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            if (c.moveToFirst()) {
1170548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                dataIdToAddSuperPrimary = c.getLong(ContactEntityQuery.DATA_ID);
1171548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            }
1172548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        } finally {
1173548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            c.close();
1174548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        }
1175548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell
1176548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        // Mark the name from contactId1 IS_SUPER_PRIMARY to make sure that the contact
1177548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        // display name does not change as a result of the join.
1178548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        if (dataIdToAddSuperPrimary != -1) {
11792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Builder builder = ContentProviderOperation.newUpdate(
1180548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                    ContentUris.withAppendedId(Data.CONTENT_URI, dataIdToAddSuperPrimary));
1181548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            builder.withValue(Data.IS_SUPER_PRIMARY, 1);
1182548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            builder.withValue(Data.IS_PRIMARY, 1);
11832b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            operations.add(builder.build());
11842b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
11852b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
11862b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        boolean success = false;
11872b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Apply all aggregation exceptions as one batch
11882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        try {
11892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            resolver.applyBatch(ContactsContract.AUTHORITY, operations);
1190886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            showToast(R.string.contactsJoinedMessage);
11912b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            success = true;
1192d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        } catch (RemoteException | OperationApplicationException e) {
11932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Log.e(TAG, "Failed to apply aggregation exception batch", e);
1194886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            showToast(R.string.contactSavedErrorToast);
11952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
11962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
11972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
11982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        if (success) {
11992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Uri uri = RawContacts.getContactLookupUri(resolver,
12002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
12012b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            callbackIntent.setData(uri);
12022b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
12033a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
12042b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
12052b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1206d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    private long[] getRawContactIdsForAggregation(long[] contactIds) {
1207d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        if (contactIds == null) {
1208d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            return null;
1209d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        }
1210d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
1211548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        final ContentResolver resolver = getContentResolver();
1212548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        long rawContactIds[];
1213d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
1214d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        final StringBuilder queryBuilder = new StringBuilder();
1215d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        final String stringContactIds[] = new String[contactIds.length];
1216d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        for (int i = 0; i < contactIds.length; i++) {
1217d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            queryBuilder.append(RawContacts.CONTACT_ID + "=?");
1218d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            stringContactIds[i] = String.valueOf(contactIds[i]);
1219d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            if (contactIds[i] == -1) {
1220d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                return null;
1221d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            }
1222d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            if (i == contactIds.length -1) {
1223d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                break;
1224d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            }
1225d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            queryBuilder.append(" OR ");
1226d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        }
1227d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
1228548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        final Cursor c = resolver.query(RawContacts.CONTENT_URI,
1229548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                JoinContactQuery.PROJECTION,
1230d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                queryBuilder.toString(),
1231d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                stringContactIds, null);
1232548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        if (c == null) {
1233548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            Log.e(TAG, "Unable to open Contacts DB cursor");
1234548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            showToast(R.string.contactSavedErrorToast);
1235548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            return null;
1236548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        }
1237548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        try {
1238548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            if (c.getCount() < 2) {
1239d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                Log.e(TAG, "Not enough raw contacts to aggregate together.");
1240548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                return null;
1241548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            }
1242548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            rawContactIds = new long[c.getCount()];
1243548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            for (int i = 0; i < rawContactIds.length; i++) {
1244548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                c.moveToPosition(i);
1245548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                long rawContactId = c.getLong(JoinContactQuery._ID);
1246548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                rawContactIds[i] = rawContactId;
1247548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            }
1248548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        } finally {
1249548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            c.close();
1250548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        }
1251548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        return rawContactIds;
1252548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell    }
1253548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell
1254d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    private long[] getRawContactIdsForAggregation(long contactId1, long contactId2) {
1255d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        return getRawContactIdsForAggregation(new long[] {contactId1, contactId2});
1256d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    }
1257d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
12582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    /**
12592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation.
12602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     */
12612b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations,
12622b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            long rawContactId1, long rawContactId2) {
12632b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Builder builder =
12642b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
12652b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
12662b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
12672b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
12682b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        operations.add(builder.build());
12692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
1270886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov
1271886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    /**
1272886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov     * Shows a toast on the UI thread.
1273886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov     */
1274886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    private void showToast(final int message) {
12753a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler.post(new Runnable() {
1276886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov
1277886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            @Override
1278886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            public void run() {
1279886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov                Toast.makeText(ContactSaveService.this, message, Toast.LENGTH_LONG).show();
1280886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            }
1281886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov        });
1282886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    }
12833a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
12843a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    private void deliverCallback(final Intent callbackIntent) {
12853a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler.post(new Runnable() {
12863a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
12873a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            @Override
12883a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            public void run() {
12893a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                deliverCallbackOnUiThread(callbackIntent);
12903a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            }
12913a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        });
12923a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
12933a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
12943a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    void deliverCallbackOnUiThread(final Intent callbackIntent) {
12953a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // TODO: this assumes that if there are multiple instances of the same
12963a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // activity registered, the last one registered is the one waiting for
12973a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // the callback. Validity of this assumption needs to be verified.
1298a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson        for (Listener listener : sListeners) {
1299a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson            if (callbackIntent.getComponent().equals(
1300a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson                    ((Activity) listener).getIntent().getComponent())) {
1301a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson                listener.onServiceCompleted(callbackIntent);
1302a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson                return;
13033a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            }
13043a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        }
13053a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1306173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann}
1307