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;
45ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelsonimport android.provider.ContactsContract.Profile;
46caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.RawContacts;
47c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoroimport android.provider.ContactsContract.RawContactsEntity;
48173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.util.Log;
492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.widget.Toast;
50173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
51dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wangimport com.android.contacts.activities.ContactEditorBaseActivity;
5267addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wangimport com.android.contacts.common.compat.CompatUtils;
53d7ca03e23948c3b2d5f97ec5598d8b50e3fc0b25Chiao Chengimport com.android.contacts.common.database.ContactUpdateUtils;
540d5588da244d0992c3ff8f25d0875fdf95a8c644Chiao Chengimport com.android.contacts.common.model.AccountTypeManager;
5567addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wangimport com.android.contacts.common.model.CPOWrapper;
56cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactDelta;
57cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactDeltaList;
58cd321f65f1e50409812976380ad1f0fdb3fa35cbYorke Leeimport com.android.contacts.common.model.RawContactModifier;
59428f008513d1591cc08fcfe2cf0c9237fb313241Chiao Chengimport com.android.contacts.common.model.account.AccountWithDataSet;
60615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shraunerimport com.android.contacts.common.util.PermissionsUtil;
61aac0e66fb100b329d6010637998849048efadca9Wenyi Wangimport com.android.contacts.compat.PinnedPositionsCompat;
620ae4a9330924726e3e06f9de96e81566e4fea635Walter Jangimport com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor.SaveMode;
63637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport com.android.contacts.util.ContactPhotoUtils;
64637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee
65e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Lists;
66e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Sets;
67e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Cheng
68c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.ArrayList;
69c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.HashSet;
70c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.List;
71c42ea4eca298419484444a57bfc2da2c83e7adb7Daniel Lehmannimport java.util.concurrent.CopyOnWriteArrayList;
72173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
7318ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov/**
7418ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov * A service responsible for saving changes to the content provider.
7518ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov */
76173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannpublic class ContactSaveService extends IntentService {
77173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    private static final String TAG = "ContactSaveService";
78173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
79a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan    /** Set to true in order to view logs on content provider operations */
80a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan    private static final boolean DEBUG = false;
81a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan
82caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String ACTION_NEW_RAW_CONTACT = "newRawContact";
83caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
84caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_ACCOUNT_NAME = "accountName";
85caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_ACCOUNT_TYPE = "accountType";
862b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    public static final String EXTRA_DATA_SET = "dataSet";
87caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_CONTENT_VALUES = "contentValues";
88caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_CALLBACK_INTENT = "callbackIntent";
89caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
90a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String ACTION_SAVE_CONTACT = "saveContact";
91a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String EXTRA_CONTACT_STATE = "state";
92a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String EXTRA_SAVE_MODE = "saveMode";
93ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson    public static final String EXTRA_SAVE_IS_PROFILE = "saveIsProfile";
9436d24d7ede42a252c82c4aa783b2231c5e2eea79Dave Santoro    public static final String EXTRA_SAVE_SUCCEEDED = "saveSucceeded";
95e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    public static final String EXTRA_UPDATED_PHOTOS = "updatedPhotos";
96173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
971ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    public static final String ACTION_CREATE_GROUP = "createGroup";
98e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String ACTION_RENAME_GROUP = "renameGroup";
99e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String ACTION_DELETE_GROUP = "deleteGroup";
1002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String ACTION_UPDATE_GROUP = "updateGroup";
101e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String EXTRA_GROUP_ID = "groupId";
102e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String EXTRA_GROUP_LABEL = "groupLabel";
1032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String EXTRA_RAW_CONTACTS_TO_ADD = "rawContactsToAdd";
1042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String EXTRA_RAW_CONTACTS_TO_REMOVE = "rawContactsToRemove";
105e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
1069d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String ACTION_SET_STARRED = "setStarred";
1077d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    public static final String ACTION_DELETE_CONTACT = "delete";
108d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    public static final String ACTION_DELETE_MULTIPLE_CONTACTS = "deleteMultipleContacts";
1099d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String EXTRA_CONTACT_URI = "contactUri";
110d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    public static final String EXTRA_CONTACT_IDS = "contactIds";
1119d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String EXTRA_STARRED_FLAG = "starred";
1129d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
1130f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary";
1140f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String ACTION_CLEAR_PRIMARY = "clearPrimary";
1150f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String EXTRA_DATA_ID = "dataId";
1160f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
1172b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String ACTION_JOIN_CONTACTS = "joinContacts";
118d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    public static final String ACTION_JOIN_SEVERAL_CONTACTS = "joinSeveralContacts";
1192b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String EXTRA_CONTACT_ID1 = "contactId1";
1202b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String EXTRA_CONTACT_ID2 = "contactId2";
1212b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
122683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String ACTION_SET_SEND_TO_VOICEMAIL = "sendToVoicemail";
123683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String EXTRA_SEND_TO_VOICEMAIL_FLAG = "sendToVoicemailFlag";
124683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
125683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String ACTION_SET_RINGTONE = "setRingtone";
126683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String EXTRA_CUSTOM_RINGTONE = "customRingtone";
127683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
128caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet(
129caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.MIMETYPE,
130caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.IS_PRIMARY,
131caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA1,
132caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA2,
133caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA3,
134caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA4,
135caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA5,
136caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA6,
137caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA7,
138caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA8,
139caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA9,
140caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA10,
141caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA11,
142caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA12,
143caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA13,
144caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA14,
145caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA15
146caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    );
147caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
148a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    private static final int PERSIST_TRIES = 3;
149a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
1500653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang    private static final int MAX_CONTACTS_PROVIDER_BATCH_SIZE = 499;
1510653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang
1523a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public interface Listener {
1533a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        public void onServiceCompleted(Intent callbackIntent);
1543a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
156a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson    private static final CopyOnWriteArrayList<Listener> sListeners =
157a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson            new CopyOnWriteArrayList<Listener>();
1583a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1593a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    private Handler mMainHandler;
1603a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
161173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    public ContactSaveService() {
162173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann        super(TAG);
163173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann        setIntentRedelivery(true);
1643a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler = new Handler(Looper.getMainLooper());
1653a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1663a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1673a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public static void registerListener(Listener listener) {
1683a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        if (!(listener instanceof Activity)) {
1693a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            throw new ClassCastException("Only activities can be registered to"
1703a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                    + " receive callback from " + ContactSaveService.class.getName());
1713a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        }
172a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson        sListeners.add(0, listener);
1733a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1743a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1753a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public static void unregisterListener(Listener listener) {
176a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson        sListeners.remove(listener);
177173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    }
178173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
179dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang    /**
180dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang     * Returns true if the ContactSaveService was started successfully and false if an exception
181dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang     * was thrown and a Toast error message was displayed.
182dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang     */
183dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang    public static boolean startService(Context context, Intent intent, int saveMode) {
184dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang        try {
185dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang            context.startService(intent);
186dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang        } catch (Exception exception) {
187dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang            final int resId;
188dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang            switch (saveMode) {
189dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang                case ContactEditorBaseActivity.ContactEditor.SaveMode.SPLIT:
190dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang                    resId = R.string.contactUnlinkErrorToast;
191dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang                    break;
192dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang                case ContactEditorBaseActivity.ContactEditor.SaveMode.RELOAD:
193dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang                    resId = R.string.contactJoinErrorToast;
194dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang                    break;
195dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang                case ContactEditorBaseActivity.ContactEditor.SaveMode.CLOSE:
196dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang                    resId = R.string.contactSavedErrorToast;
197dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang                    break;
198dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang                default:
199dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang                    resId = R.string.contactGenericErrorToast;
200dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang            }
201dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang            Toast.makeText(context, resId, Toast.LENGTH_SHORT).show();
202dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang            return false;
203dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang        }
204dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang        return true;
205dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang    }
206dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang
207dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang    /**
208dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang     * Utility method that starts service and handles exception.
209dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang     */
210dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang    public static void startService(Context context, Intent intent) {
211dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang        try {
212dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang            context.startService(intent);
213dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang        } catch (Exception exception) {
214dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang            Toast.makeText(context, R.string.contactGenericErrorToast, Toast.LENGTH_SHORT).show();
215dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang        }
216dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang    }
217dd7d456a080f48d93aa3a9f9b04da0f17a8833a7Wenyi Wang
218173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    @Override
219a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public Object getSystemService(String name) {
220a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Object service = super.getSystemService(name);
221a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        if (service != null) {
222a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            return service;
223a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
224a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
225a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return getApplicationContext().getSystemService(name);
226a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
227a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
228a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    @Override
229173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    protected void onHandleIntent(Intent intent) {
2303a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner        if (intent == null) {
2313a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner            Log.d(TAG, "onHandleIntent: could not handle null intent");
2323a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner            return;
2333a7cc76a5fdd41af0b8da0e6e27adbba51b73e52Jay Shrauner        }
234615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner        if (!PermissionsUtil.hasPermission(this, WRITE_CONTACTS)) {
235615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner            Log.w(TAG, "No WRITE_CONTACTS permission, unable to write to CP2");
236615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner            // TODO: add more specific error string such as "Turn on Contacts
237615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner            // permission to update your contacts"
238615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner            showToast(R.string.contactSavedErrorToast);
239615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner            return;
240615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner        }
241615ed9c5e89f06a56ed6ad30244a9f6cb495e85cJay Shrauner
2422f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa        // Call an appropriate method. If we're sure it affects how incoming phone calls are
2432f21c44104f4c1376ef2147c472555f43990af26Daisuke Miyakawa        // handled, then notify the fact to in-call screen.
244caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String action = intent.getAction();
245caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        if (ACTION_NEW_RAW_CONTACT.equals(action)) {
246caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            createRawContact(intent);
247a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        } else if (ACTION_SAVE_CONTACT.equals(action)) {
248a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            saveContact(intent);
2491ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        } else if (ACTION_CREATE_GROUP.equals(action)) {
2501ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov            createGroup(intent);
251e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        } else if (ACTION_RENAME_GROUP.equals(action)) {
252e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            renameGroup(intent);
253e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        } else if (ACTION_DELETE_GROUP.equals(action)) {
254e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            deleteGroup(intent);
2552d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        } else if (ACTION_UPDATE_GROUP.equals(action)) {
2562d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            updateGroup(intent);
2579d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        } else if (ACTION_SET_STARRED.equals(action)) {
2589d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            setStarred(intent);
2590f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) {
2600f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            setSuperPrimary(intent);
2610f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        } else if (ACTION_CLEAR_PRIMARY.equals(action)) {
2620f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            clearPrimary(intent);
263d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        } else if (ACTION_DELETE_MULTIPLE_CONTACTS.equals(action)) {
264d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell            deleteMultipleContacts(intent);
2657d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        } else if (ACTION_DELETE_CONTACT.equals(action)) {
2667d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            deleteContact(intent);
2672b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } else if (ACTION_JOIN_CONTACTS.equals(action)) {
2682b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            joinContacts(intent);
269d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        } else if (ACTION_JOIN_SEVERAL_CONTACTS.equals(action)) {
270d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            joinSeveralContacts(intent);
271683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) {
272683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            setSendToVoicemail(intent);
273683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        } else if (ACTION_SET_RINGTONE.equals(action)) {
274683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            setRingtone(intent);
275caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
276caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
277caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
2789d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    /**
2799d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to create a new raw contact
2809d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * using data presented as a set of ContentValues.
2819d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     */
2829d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createNewRawContactIntent(Context context,
2832b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            ArrayList<ContentValues> values, AccountWithDataSet account,
284e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction) {
2859d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(
2869d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov                context, ContactSaveService.class);
2879d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT);
2889d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        if (account != null) {
2899d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
2909d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
2912b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet);
2929d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        }
2939d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putParcelableArrayListExtra(
2949d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov                ContactSaveService.EXTRA_CONTENT_VALUES, values);
2959d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
2969d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // Callback intent will be invoked by the service once the new contact is
2979d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // created.  The service will put the URI of the new contact as "data" on
2989d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // the callback intent.
2999d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
3009d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        callbackIntent.setAction(callbackAction);
3019d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
3029d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        return serviceIntent;
3039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    }
3049d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
305caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    private void createRawContact(Intent intent) {
306caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
307caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
3082b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String dataSet = intent.getStringExtra(EXTRA_DATA_SET);
309caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES);
310caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
311caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
312caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
313caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
314caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .withValue(RawContacts.ACCOUNT_NAME, accountName)
315caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .withValue(RawContacts.ACCOUNT_TYPE, accountType)
3162b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                .withValue(RawContacts.DATA_SET, dataSet)
317caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .build());
318caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
319caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        int size = valueList.size();
320caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        for (int i = 0; i < size; i++) {
321caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            ContentValues values = valueList.get(i);
322caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            values.keySet().retainAll(ALLOWED_DATA_COLUMNS);
323caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
324caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .withValueBackReference(Data.RAW_CONTACT_ID, 0)
325caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .withValues(values)
326caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .build());
327caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
328caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
329caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ContentResolver resolver = getContentResolver();
330caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ContentProviderResult[] results;
331caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        try {
332caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            results = resolver.applyBatch(ContactsContract.AUTHORITY, operations);
333caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        } catch (Exception e) {
334caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            throw new RuntimeException("Failed to store new contact", e);
335caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
336caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
337caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Uri rawContactUri = results[0].uri;
338caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri));
339caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
3403a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
341caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
342caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
343caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    /**
344a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     * Creates an intent that can be sent to this service to create a new raw contact
345a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     * using data presented as a set of ContentValues.
346e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * This variant is more convenient to use when there is only one photo that can
347e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * possibly be updated, as in the Contact Details screen.
348e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * @param rawContactId identifies a writable raw-contact whose photo is to be updated.
349e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * @param updatedPhotoPath denotes a temporary file containing the contact's new photo.
350a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     */
351851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu    public static Intent createSaveContactIntent(Context context, RawContactDeltaList state,
352e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            String saveModeExtraKey, int saveMode, boolean isProfile,
353e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction, long rawContactId,
354637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            Uri updatedPhotoPath) {
355e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        Bundle bundle = new Bundle();
356637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        bundle.putParcelable(String.valueOf(rawContactId), updatedPhotoPath);
357e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        return createSaveContactIntent(context, state, saveModeExtraKey, saveMode, isProfile,
358e3373dceb689209533e95a2cfbfbf1d9008ab6f6Walter Jang                callbackActivity, callbackAction, bundle,
359e3373dceb689209533e95a2cfbfbf1d9008ab6f6Walter Jang                /* joinContactIdExtraKey */ null, /* joinContactId */ null);
360e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    }
361e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
362e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    /**
363e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * Creates an intent that can be sent to this service to create a new raw contact
364e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * using data presented as a set of ContentValues.
365e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * This variant is used when multiple contacts' photos may be updated, as in the
366e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * Contact Editor.
367e3373dceb689209533e95a2cfbfbf1d9008ab6f6Walter Jang     *
368e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * @param updatedPhotos maps each raw-contact's ID to the file-path of the new photo.
369e3373dceb689209533e95a2cfbfbf1d9008ab6f6Walter Jang     * @param joinContactIdExtraKey the key used to pass the joinContactId in the callback intent.
370e3373dceb689209533e95a2cfbfbf1d9008ab6f6Walter Jang     * @param joinContactId the raw contact ID to join to the contact after doing the save.
371e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     */
372851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu    public static Intent createSaveContactIntent(Context context, RawContactDeltaList state,
373e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            String saveModeExtraKey, int saveMode, boolean isProfile,
374e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction,
375e3373dceb689209533e95a2cfbfbf1d9008ab6f6Walter Jang            Bundle updatedPhotos, String joinContactIdExtraKey, Long joinContactId) {
376f8c8ac348cf217766a6626a5b7191a0de11f06d3Walter Jang        Intent serviceIntent = new Intent(
377f8c8ac348cf217766a6626a5b7191a0de11f06d3Walter Jang                context, ContactSaveService.class);
378a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT);
379a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state);
380ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson        serviceIntent.putExtra(EXTRA_SAVE_IS_PROFILE, isProfile);
3813a4e7a282867bfe4803940eca40e35094507605bbenny.lin        serviceIntent.putExtra(EXTRA_SAVE_MODE, saveMode);
3823a4e7a282867bfe4803940eca40e35094507605bbenny.lin
383e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        if (updatedPhotos != null) {
384e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus            serviceIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos);
385e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        }
386a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
387e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        if (callbackActivity != null) {
388e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            // Callback intent will be invoked by the service once the contact is
389e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            // saved.  The service will put the URI of the new contact as "data" on
390e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            // the callback intent.
391e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Intent callbackIntent = new Intent(context, callbackActivity);
392e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            callbackIntent.putExtra(saveModeExtraKey, saveMode);
393e3373dceb689209533e95a2cfbfbf1d9008ab6f6Walter Jang            if (joinContactIdExtraKey != null && joinContactId != null) {
394e3373dceb689209533e95a2cfbfbf1d9008ab6f6Walter Jang                callbackIntent.putExtra(joinContactIdExtraKey, joinContactId);
395e3373dceb689209533e95a2cfbfbf1d9008ab6f6Walter Jang            }
396e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            callbackIntent.setAction(callbackAction);
397e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
398e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        }
399a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return serviceIntent;
400a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
401a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
402a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    private void saveContact(Intent intent) {
403851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu        RawContactDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE);
404ead19c5eafee0ffb43b02a4ae75ac5244ad3f853Isaac Katzenelson        boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false);
405e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        Bundle updatedPhotos = intent.getParcelableExtra(EXTRA_UPDATED_PHOTOS);
406a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
4070809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner        if (state == null) {
4080809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner            Log.e(TAG, "Invalid arguments for saveContact request");
4090809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner            return;
4100809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner        }
4110809978e5d3f87687e1fd986130897deca1f74cdJay Shrauner
4123a4e7a282867bfe4803940eca40e35094507605bbenny.lin        int saveMode = intent.getIntExtra(EXTRA_SAVE_MODE, -1);
413a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // Trim any empty fields, and RawContacts, before persisting
414a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
415851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu        RawContactModifier.trimEmpty(state, accountTypes);
416a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
417a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Uri lookupUri = null;
418a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
419a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final ContentResolver resolver = getContentResolver();
420ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang
421e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        boolean succeeded = false;
422a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
423ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        // Keep track of the id of a newly raw-contact (if any... there can be at most one).
424ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        long insertedRawContactId = -1;
425ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus
426a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // Attempt to persist changes
427a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        int tries = 0;
428a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        while (tries++ < PERSIST_TRIES) {
429a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            try {
430a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Build operations and try applying
43167addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang                final ArrayList<CPOWrapper> diffWrapper = state.buildDiffWrapper();
43267addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang
43367addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang                final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
43467addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang
43567addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang                for (CPOWrapper cpoWrapper : diffWrapper) {
43667addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang                    diff.add(cpoWrapper.getOperation());
43767addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang                }
438ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang
439a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                if (DEBUG) {
440a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    Log.v(TAG, "Content Provider Operations:");
441a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    for (ContentProviderOperation operation : diff) {
442a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                        Log.v(TAG, operation.toString());
443a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    }
444a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                }
445a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan
446ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                int numberProcessed = 0;
447ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                boolean batchFailed = false;
448ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                final ContentProviderResult[] results = new ContentProviderResult[diff.size()];
449ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                while (numberProcessed < diff.size()) {
450ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                    final int subsetCount = applyDiffSubset(diff, numberProcessed, results, resolver);
451ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                    if (subsetCount == -1) {
452511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                        Log.w(TAG, "Resolver.applyBatch failed in saveContacts");
453ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                        batchFailed = true;
454ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                        break;
455ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                    } else {
456ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                        numberProcessed += subsetCount;
457511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                    }
458a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
459a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
460ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                if (batchFailed) {
461ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                    // Retry save
462ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                    continue;
463ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                }
464ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang
46567addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang                final long rawContactId = getRawContactId(state, diffWrapper, results);
466a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (rawContactId == -1) {
467a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    throw new IllegalStateException("Could not determine RawContact ID after save");
468a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
469ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                // We don't have to check to see if the value is still -1.  If we reach here,
470ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                // the previous loop iteration didn't succeed, so any ID that we obtained is bogus.
47167addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang                insertedRawContactId = getInsertedRawContactId(diffWrapper, results);
4727c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                if (isProfile) {
4737c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    // Since the profile supports local raw contacts, which may have been completely
4747c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    // removed if all information was removed, we need to do a special query to
4757c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    // get the lookup URI for the profile contact (if it still exists).
4767c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    Cursor c = resolver.query(Profile.CONTENT_URI,
4777c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                            new String[] {Contacts._ID, Contacts.LOOKUP_KEY},
4787c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                            null, null, null);
479e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner                    if (c == null) {
480e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner                        continue;
481e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner                    }
4827c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    try {
483162b7e34fb581d0fa279957af5136d190c40759fErik                        if (c.moveToFirst()) {
484162b7e34fb581d0fa279957af5136d190c40759fErik                            final long contactId = c.getLong(0);
485162b7e34fb581d0fa279957af5136d190c40759fErik                            final String lookupKey = c.getString(1);
486162b7e34fb581d0fa279957af5136d190c40759fErik                            lookupUri = Contacts.getLookupUri(contactId, lookupKey);
487162b7e34fb581d0fa279957af5136d190c40759fErik                        }
4887c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    } finally {
4897c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                        c.close();
4907c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    }
4917c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                } else {
4927c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
4937c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                                    rawContactId);
4947c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                    lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri);
4957c34c0a46434b2669b0fdba2c9e0e4cce5675f94Dave Santoro                }
496e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner                if (lookupUri != null) {
497e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner                    Log.v(TAG, "Saved contact. New URI: " + lookupUri);
498e320c0bcd539f9426db9b694147e845fe0e2d59dJay Shrauner                }
499e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
500e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                // We can change this back to false later, if we fail to save the contact photo.
501e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                succeeded = true;
502a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                break;
503a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
504a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            } catch (RemoteException e) {
505a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Something went wrong, bail without success
506a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                Log.e(TAG, "Problem persisting user edits", e);
507a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                break;
508a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
50957fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner            } catch (IllegalArgumentException e) {
51057fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner                // This is thrown by applyBatch on malformed requests
51157fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner                Log.e(TAG, "Problem persisting user edits", e);
51257fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner                showToast(R.string.contactSavedErrorToast);
51357fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner                break;
51457fca1851e5371f259d4dd6bdf322e20c606c975Jay Shrauner
515a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            } catch (OperationApplicationException e) {
516a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Version consistency failed, re-parent change and try again
517a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString());
518a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN(");
519a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                boolean first = true;
520a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final int count = state.size();
521a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                for (int i = 0; i < count; i++) {
522a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    Long rawContactId = state.getRawContactId(i);
523a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    if (rawContactId != null && rawContactId != -1) {
524a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        if (!first) {
525a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                            sb.append(',');
526a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        }
527a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        sb.append(rawContactId);
528a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        first = false;
529a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    }
530a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
531a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                sb.append(")");
532a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
533a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (first) {
5343b6c628fc9e717a41c2954b1101c3a04ad382c55Brian Attwell                    throw new IllegalStateException(
5353b6c628fc9e717a41c2954b1101c3a04ad382c55Brian Attwell                            "Version consistency failed for a new contact", e);
536a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
537a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
538851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu                final RawContactDeltaList newState = RawContactDeltaList.fromQuery(
539c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                        isProfile
540c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                                ? RawContactsEntity.PROFILE_CONTENT_URI
541c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                                : RawContactsEntity.CONTENT_URI,
542c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                        resolver, sb.toString(), null, null);
543851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu                state = RawContactDeltaList.mergeAfter(newState, state);
544c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro
545c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                // Update the new state to use profile URIs if appropriate.
546c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                if (isProfile) {
547851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu                    for (RawContactDelta delta : state) {
548c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                        delta.setProfileQueryUri();
549c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                    }
550c90f95e63684363d10ffe5ef8f08f2159fb5bfc0Dave Santoro                }
551a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            }
552a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
553a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
554e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        // Now save any updated photos.  We do this at the end to ensure that
555e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        // the ContactProvider already knows about newly-created contacts.
556e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        if (updatedPhotos != null) {
557e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus            for (String key : updatedPhotos.keySet()) {
558637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee                Uri photoUri = updatedPhotos.getParcelable(key);
559e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                long rawContactId = Long.parseLong(key);
560ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus
561ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                // If the raw-contact ID is negative, we are saving a new raw-contact;
562ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                // replace the bogus ID with the new one that we actually saved the contact at.
563ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                if (rawContactId < 0) {
564ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                    rawContactId = insertedRawContactId;
565ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus                }
566ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus
567511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                // If the save failed, insertedRawContactId will be -1
568c4698fbe3a5a94de639f91b01c2cb02b61d51331Jay Shrauner                if (rawContactId < 0 || !saveUpdatedPhoto(rawContactId, photoUri, saveMode)) {
569511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                    succeeded = false;
570511561dd8ef9e6cac3c62e138193ef48e6d79760Jay Shrauner                }
571e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus            }
572e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        }
573e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
574e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
575e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        if (callbackIntent != null) {
576e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            if (succeeded) {
577e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus                // Mark the intent to indicate that the save was successful (even if the lookup URI
578e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus                // is now null).  For local contacts or the local profile, it's possible that the
579e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus                // save triggered removal of the contact, so no lookup URI would exist..
580e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus                callbackIntent.putExtra(EXTRA_SAVE_SUCCEEDED, true);
581e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            }
582e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            callbackIntent.setData(lookupUri);
583e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            deliverCallback(callbackIntent);
584e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus        }
585a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
586a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
587e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    /**
588ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang     * Splits "diff" into subsets based on "MAX_CONTACTS_PROVIDER_BATCH_SIZE", applies each of the
589ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang     * subsets, adds the returned array to "results".
590ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang     *
591ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang     * @return the size of the array, if not null; -1 when the array is null.
592ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang     */
593ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang    private int applyDiffSubset(ArrayList<ContentProviderOperation> diff, int offset,
594ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang            ContentProviderResult[] results, ContentResolver resolver)
595ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang            throws RemoteException, OperationApplicationException {
596ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang        final int subsetCount = Math.min(diff.size() - offset, MAX_CONTACTS_PROVIDER_BATCH_SIZE);
597ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang        final ArrayList<ContentProviderOperation> subset = new ArrayList<>();
598ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang        subset.addAll(diff.subList(offset, offset + subsetCount));
599ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang        final ContentProviderResult[] subsetResult = resolver.applyBatch(ContactsContract
600ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang                .AUTHORITY, subset);
601ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang        if (subsetResult == null || (offset + subsetResult.length) > results.length) {
602ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang            return -1;
603ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang        }
604ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang        for (ContentProviderResult c : subsetResult) {
605ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang            results[offset++] = c;
606ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang        }
607ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang        return subsetResult.length;
608ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang    }
609ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang
610ccdf69cb069764a971bf0173f3440cdbece58be3Wenyi Wang    /**
611e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * Save updated photo for the specified raw-contact.
612e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     * @return true for success, false for failure
613e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus     */
6143a4e7a282867bfe4803940eca40e35094507605bbenny.lin    private boolean saveUpdatedPhoto(long rawContactId, Uri photoUri, int saveMode) {
615ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        final Uri outputUri = Uri.withAppendedPath(
616e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
617e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus                RawContacts.DisplayPhoto.CONTENT_DIRECTORY);
618e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
6193a4e7a282867bfe4803940eca40e35094507605bbenny.lin        return ContactPhotoUtils.savePhotoFromUriToUri(this, photoUri, outputUri, (saveMode == 0));
620e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus    }
621e692e010ca02200087997280e7c239ebf94aa8f9Josh Gargus
622ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus    /**
623ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus     * Find the ID of an existing or newly-inserted raw-contact.  If none exists, return -1.
624ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus     */
625851222a96b5d68602fb361ea3527101e893f67e3Maurice Chu    private long getRawContactId(RawContactDeltaList state,
62667addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang            final ArrayList<CPOWrapper> diffWrapper,
627a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            final ContentProviderResult[] results) {
628ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        long existingRawContactId = state.findRawContactId();
629ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus        if (existingRawContactId != -1) {
630ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus            return existingRawContactId;
631a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
632a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
63367addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang        return getInsertedRawContactId(diffWrapper, results);
634ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus    }
635ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus
636ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus    /**
637ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus     * Find the ID of a newly-inserted raw-contact.  If none exists, return -1.
638ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus     */
639ef15c8e4a1093ac9d6bb0e9aed10a130e8e79be5Josh Gargus    private long getInsertedRawContactId(
64067addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang            final ArrayList<CPOWrapper> diffWrapper, final ContentProviderResult[] results) {
641568f4e72711908455ccd20fbb04c1017b10d7e1cJay Shrauner        if (results == null) {
642568f4e72711908455ccd20fbb04c1017b10d7e1cJay Shrauner            return -1;
643568f4e72711908455ccd20fbb04c1017b10d7e1cJay Shrauner        }
64467addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang        final int diffSize = diffWrapper.size();
6453d7edc3ef4f2521639bae91c93a8238e5a9509c1Jay Shrauner        final int numResults = results.length;
6463d7edc3ef4f2521639bae91c93a8238e5a9509c1Jay Shrauner        for (int i = 0; i < diffSize && i < numResults; i++) {
64767addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang            final CPOWrapper cpoWrapper = diffWrapper.get(i);
64867addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang            final boolean isInsert = CompatUtils.isInsertCompat(cpoWrapper);
64967addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang            if (isInsert && cpoWrapper.getOperation().getUri().getEncodedPath().contains(
65067addcce8b54636c9e38f18db3bd262861aa3040Wenyi Wang                    RawContacts.CONTENT_URI.getEncodedPath())) {
651a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                return ContentUris.parseId(results[i].uri);
652a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            }
653a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
654a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return -1;
655a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
656a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
657a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    /**
658717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * Creates an intent that can be sent to this service to create a new group as
659717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * well as add new members at the same time.
660717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     *
661717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param context of the application
662717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param account in which the group should be created
663717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param label is the name of the group (cannot be null)
664717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param rawContactsToAdd is an array of raw contact IDs for contacts that
665717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     *            should be added to the group
666717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param callbackActivity is the activity to send the callback intent to
667717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param callbackAction is the intent action for the callback intent
668caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov     */
6692b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    public static Intent createNewGroupIntent(Context context, AccountWithDataSet account,
670e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            String label, long[] rawContactsToAdd, Class<? extends Activity> callbackActivity,
671717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            String callbackAction) {
6729d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
6739d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP);
6749d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
6759d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
6762b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet);
6779d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label);
678717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
679caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
6809d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // Callback intent will be invoked by the service once the new group is
681717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // created.
6829d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
6839d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        callbackIntent.setAction(callbackAction);
6841ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
6859d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
6861ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        return serviceIntent;
6871ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    }
6881ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
6891ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    private void createGroup(Intent intent) {
6902b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
6912b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
6922b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String dataSet = intent.getStringExtra(EXTRA_DATA_SET);
6932b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
694717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
6951ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
6961ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        ContentValues values = new ContentValues();
6971ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.ACCOUNT_TYPE, accountType);
6981ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.ACCOUNT_NAME, accountName);
6992b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        values.put(Groups.DATA_SET, dataSet);
7001ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.TITLE, label);
7011ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
702717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final ContentResolver resolver = getContentResolver();
703717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
704717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Create the new group
705717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values);
706717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
707717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // If there's no URI, then the insertion failed. Abort early because group members can't be
708717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // added if the group doesn't exist
7091ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        if (groupUri == null) {
710717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            Log.e(TAG, "Couldn't create group with label " + label);
7111ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov            return;
7121ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        }
7131ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
714717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Add new group members
715717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri));
716717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
717717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // TODO: Move this into the contact editor where it belongs. This needs to be integrated
718717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // with the way other intent extras that are passed to the {@link ContactEditorActivity}.
7191ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.clear();
7201ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
7211ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri));
7221ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
7231ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
724c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setData(groupUri);
725717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // TODO: This can be taken out when the above TODO is addressed
7261ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values));
7273a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
7281ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    }
7291ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
7301ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    /**
7319d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to rename a group.
7321ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov     */
733c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan    public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel,
734e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction) {
7359d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
7369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP);
7379d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
7389d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
739c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
740c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        // Callback intent will be invoked by the service once the group is renamed.
741c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        Intent callbackIntent = new Intent(context, callbackActivity);
742c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setAction(callbackAction);
743c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
744c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
745caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        return serviceIntent;
746caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
747e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
748e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    private void renameGroup(Intent intent) {
749e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
750e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
751e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
752e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        if (groupId == -1) {
753e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            Log.e(TAG, "Invalid arguments for renameGroup request");
754e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            return;
755e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        }
756e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
757e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        ContentValues values = new ContentValues();
758e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        values.put(Groups.TITLE, label);
759c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
760c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        getContentResolver().update(groupUri, values, null, null);
761c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
762c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
763c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setData(groupUri);
764c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        deliverCallback(callbackIntent);
765e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
766e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
767e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    /**
7689d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to delete a group.
769e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov     */
7709d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createGroupDeletionIntent(Context context, long groupId) {
7719d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
7729d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP);
773e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
774e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        return serviceIntent;
775e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
776e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
777e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    private void deleteGroup(Intent intent) {
778e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
779e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        if (groupId == -1) {
780e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            Log.e(TAG, "Invalid arguments for deleteGroup request");
781e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            return;
782e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        }
783e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
784e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        getContentResolver().delete(
785e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov                ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null);
786e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
787e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
788e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    /**
7892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * Creates an intent that can be sent to this service to rename a group as
7902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * well as add and remove members from the group.
7912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *
7922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param context of the application
7932d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param groupId of the group that should be modified
7942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param newLabel is the updated name of the group (can be null if the name
7952d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            should not be updated)
7962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param rawContactsToAdd is an array of raw contact IDs for contacts that
7972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            should be added to the group
7982d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param rawContactsToRemove is an array of raw contact IDs for contacts
7992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            that should be removed from the group
8002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param callbackActivity is the activity to send the callback intent to
8012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param callbackAction is the intent action for the callback intent
8022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     */
8032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel,
8042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            long[] rawContactsToAdd, long[] rawContactsToRemove,
805e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Class<? extends Activity> callbackActivity, String callbackAction) {
8062d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        Intent serviceIntent = new Intent(context, ContactSaveService.class);
8072d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP);
8082d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
8092d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
8102d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
8112d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_REMOVE,
8122d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactsToRemove);
8132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
8142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        // Callback intent will be invoked by the service once the group is updated
8152d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        Intent callbackIntent = new Intent(context, callbackActivity);
8162d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        callbackIntent.setAction(callbackAction);
8172d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
8182d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
8192d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        return serviceIntent;
8202d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    }
8212d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
8222d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    private void updateGroup(Intent intent) {
8232d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
8242d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
8252d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
8262d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long[] rawContactsToRemove = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_REMOVE);
8272d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
8282d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        if (groupId == -1) {
8292d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            Log.e(TAG, "Invalid arguments for updateGroup request");
8302d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            return;
8312d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
8322d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
8332d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        final ContentResolver resolver = getContentResolver();
8342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
8352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
8362d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        // Update group name if necessary
8372d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        if (label != null) {
8382d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            ContentValues values = new ContentValues();
8392d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            values.put(Groups.TITLE, label);
840717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            resolver.update(groupUri, values, null, null);
8412d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
8422d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
843717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Add and remove members if necessary
844717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        addMembersToGroup(resolver, rawContactsToAdd, groupId);
845717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        removeMembersFromGroup(resolver, rawContactsToRemove, groupId);
846717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
847717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
848717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        callbackIntent.setData(groupUri);
849717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        deliverCallback(callbackIntent);
850717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    }
851717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
85218958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann    private static void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd,
853717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            long groupId) {
854717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        if (rawContactsToAdd == null) {
855717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            return;
856717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        }
8572d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        for (long rawContactId : rawContactsToAdd) {
8582d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            try {
8592d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ArrayList<ContentProviderOperation> rawContactOperations =
8602d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        new ArrayList<ContentProviderOperation>();
8612d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
8622d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Build an assert operation to ensure the contact is not already in the group
8632d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ContentProviderOperation.Builder assertBuilder = ContentProviderOperation
8642d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        .newAssertQuery(Data.CONTENT_URI);
8652d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                assertBuilder.withSelection(Data.RAW_CONTACT_ID + "=? AND " +
8662d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
8672d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        new String[] { String.valueOf(rawContactId),
8682d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
8692d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                assertBuilder.withExpectedCount(0);
8702d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactOperations.add(assertBuilder.build());
8712d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
8722d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Build an insert operation to add the contact to the group
8732d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ContentProviderOperation.Builder insertBuilder = ContentProviderOperation
8742d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        .newInsert(Data.CONTENT_URI);
8752d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(Data.RAW_CONTACT_ID, rawContactId);
8762d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
8772d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(GroupMembership.GROUP_ROW_ID, groupId);
8782d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactOperations.add(insertBuilder.build());
8792d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
8802d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                if (DEBUG) {
8812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    for (ContentProviderOperation operation : rawContactOperations) {
8822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        Log.v(TAG, operation.toString());
8832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    }
8842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                }
8852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
8862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Apply batch
8872d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                if (!rawContactOperations.isEmpty()) {
88818958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann                    resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations);
8892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                }
8902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            } catch (RemoteException e) {
8912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Something went wrong, bail without success
8922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                Log.e(TAG, "Problem persisting user edits for raw contact ID " +
8932d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(rawContactId), e);
8942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            } catch (OperationApplicationException e) {
8952d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // The assert could have failed because the contact is already in the group,
8962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // just continue to the next contact
8972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                Log.w(TAG, "Assert failed in adding raw contact ID " +
8982d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(rawContactId) + ". Already exists in group " +
8992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(groupId), e);
9002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            }
9012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
902717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    }
9032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
90418958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann    private static void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove,
905717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            long groupId) {
906717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        if (rawContactsToRemove == null) {
907717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            return;
908717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        }
9092d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        for (long rawContactId : rawContactsToRemove) {
9102d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // Apply the delete operation on the data row for the given raw contact's
9112d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // membership in the given group. If no contact matches the provided selection, then
9122d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // nothing will be done. Just continue to the next contact.
91318958a29b3eddb6fc42cf651ec0eed27103f534dDaniel Lehmann            resolver.delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " +
9142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
9152d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    new String[] { String.valueOf(rawContactId),
9162d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
9172d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
9182d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    }
9192d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
9202d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    /**
9219d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to star or un-star a contact.
922e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov     */
9239d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) {
9249d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
9259d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED);
9269d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
9279d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value);
9289d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
929e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        return serviceIntent;
930e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
9319d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
9329d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    private void setStarred(Intent intent) {
9339d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
9349d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false);
9359d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        if (contactUri == null) {
9369d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            Log.e(TAG, "Invalid arguments for setStarred request");
9379d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            return;
9389d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        }
9399d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
9409d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        final ContentValues values = new ContentValues(1);
9419d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        values.put(Contacts.STARRED, value);
9429d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        getContentResolver().update(contactUri, values, null, null);
943e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee
944e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        // Undemote the contact if necessary
945e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        final Cursor c = getContentResolver().query(contactUri, new String[] {Contacts._ID},
946e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee                null, null, null);
947c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner        if (c == null) {
948c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner            return;
949c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner        }
950e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        try {
951e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee            if (c.moveToFirst()) {
952e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee                final long id = c.getLong(0);
953bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee
954bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee                // Don't bother undemoting if this contact is the user's profile.
955bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee                if (id < Profile.MIN_ID) {
956aac0e66fb100b329d6010637998849048efadca9Wenyi Wang                    PinnedPositionsCompat.undemote(getContentResolver(), id);
957bbb8c99a34061911c800bbd1981b74fb7f5b5a9dYorke Lee                }
958e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee            }
959e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        } finally {
960e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee            c.close();
961e8e3fb83f3805d0636e5757a4d91db74796518e3Yorke Lee        }
9629d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    }
9630f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
9640f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    /**
965683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     * Creates an intent that can be sent to this service to set the redirect to voicemail.
966683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     */
967683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static Intent createSetSendToVoicemail(Context context, Uri contactUri,
968683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            boolean value) {
969683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Intent serviceIntent = new Intent(context, ContactSaveService.class);
970683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.setAction(ContactSaveService.ACTION_SET_SEND_TO_VOICEMAIL);
971683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
972683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_SEND_TO_VOICEMAIL_FLAG, value);
973683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
974683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        return serviceIntent;
975683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
976683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
977683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    private void setSendToVoicemail(Intent intent) {
978683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
979683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        boolean value = intent.getBooleanExtra(EXTRA_SEND_TO_VOICEMAIL_FLAG, false);
980683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        if (contactUri == null) {
981683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            Log.e(TAG, "Invalid arguments for setRedirectToVoicemail");
982683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            return;
983683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        }
984683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
985683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        final ContentValues values = new ContentValues(1);
986683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        values.put(Contacts.SEND_TO_VOICEMAIL, value);
987683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        getContentResolver().update(contactUri, values, null, null);
988683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
989683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
990683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    /**
991683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     * Creates an intent that can be sent to this service to save the contact's ringtone.
992683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     */
993683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static Intent createSetRingtone(Context context, Uri contactUri,
994683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            String value) {
995683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Intent serviceIntent = new Intent(context, ContactSaveService.class);
996683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.setAction(ContactSaveService.ACTION_SET_RINGTONE);
997683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
998683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CUSTOM_RINGTONE, value);
999683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
1000683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        return serviceIntent;
1001683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
1002683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
1003683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    private void setRingtone(Intent intent) {
1004683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
1005683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        String value = intent.getStringExtra(EXTRA_CUSTOM_RINGTONE);
1006683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        if (contactUri == null) {
1007683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            Log.e(TAG, "Invalid arguments for setRingtone");
1008683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            return;
1009683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        }
1010683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        ContentValues values = new ContentValues(1);
1011683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        values.put(Contacts.CUSTOM_RINGTONE, value);
1012683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        getContentResolver().update(contactUri, values, null, null);
1013683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
1014683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
1015683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    /**
10160f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * Creates an intent that sets the selected data item as super primary (default)
10170f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     */
10180f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static Intent createSetSuperPrimaryIntent(Context context, long dataId) {
10190f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        Intent serviceIntent = new Intent(context, ContactSaveService.class);
10200f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY);
10210f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
10220f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        return serviceIntent;
10230f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
10240f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
10250f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    private void setSuperPrimary(Intent intent) {
10260f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
10270f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        if (dataId == -1) {
10280f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            Log.e(TAG, "Invalid arguments for setSuperPrimary request");
10290f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            return;
10300f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        }
10310f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
1032d7ca03e23948c3b2d5f97ec5598d8b50e3fc0b25Chiao Cheng        ContactUpdateUtils.setSuperPrimary(this, dataId);
10330f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
10340f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
10350f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    /**
10360f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * Creates an intent that clears the primary flag of all data items that belong to the same
10370f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * raw_contact as the given data item. Will only clear, if the data item was primary before
10380f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * this call
10390f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     */
10400f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static Intent createClearPrimaryIntent(Context context, long dataId) {
10410f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        Intent serviceIntent = new Intent(context, ContactSaveService.class);
10420f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY);
10430f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
10440f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        return serviceIntent;
10450f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
10460f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
10470f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    private void clearPrimary(Intent intent) {
10480f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
10490f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        if (dataId == -1) {
10500f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            Log.e(TAG, "Invalid arguments for clearPrimary request");
10510f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            return;
10520f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        }
10530f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
10540f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        // Update the primary values in the data record.
10550f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        ContentValues values = new ContentValues(1);
10560f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_SUPER_PRIMARY, 0);
10570f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_PRIMARY, 0);
10580f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
10590f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
10600f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann                values, null, null);
10610f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
10627d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
10637d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    /**
10647d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov     * Creates an intent that can be sent to this service to delete a contact.
10657d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov     */
10667d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    public static Intent createDeleteContactIntent(Context context, Uri contactUri) {
10677d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
10687d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT);
10697d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
10707d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        return serviceIntent;
10717d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    }
10727d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
1073d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    /**
1074d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell     * Creates an intent that can be sent to this service to delete multiple contacts.
1075d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell     */
1076d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    public static Intent createDeleteMultipleContactsIntent(Context context,
1077d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell            long[] contactIds) {
1078d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        Intent serviceIntent = new Intent(context, ContactSaveService.class);
1079d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        serviceIntent.setAction(ContactSaveService.ACTION_DELETE_MULTIPLE_CONTACTS);
1080d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_IDS, contactIds);
1081d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        return serviceIntent;
1082d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    }
1083d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell
10847d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    private void deleteContact(Intent intent) {
10857d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
10867d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        if (contactUri == null) {
10877d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            Log.e(TAG, "Invalid arguments for deleteContact request");
10887d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            return;
10897d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        }
10907d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
10917d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        getContentResolver().delete(contactUri, null, null);
10927d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    }
10932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1094d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    private void deleteMultipleContacts(Intent intent) {
1095d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        final long[] contactIds = intent.getLongArrayExtra(EXTRA_CONTACT_IDS);
1096d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        if (contactIds == null) {
1097d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell            Log.e(TAG, "Invalid arguments for deleteMultipleContacts request");
1098d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell            return;
1099d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        }
1100d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        for (long contactId : contactIds) {
1101d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell            final Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
1102d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell            getContentResolver().delete(contactUri, null, null);
1103d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell        }
1104687d218928bd188a13cc32a3c553590fe0d564b1Wenyi Wang        final String deleteToastMessage = getResources().getQuantityString(R.plurals
1105687d218928bd188a13cc32a3c553590fe0d564b1Wenyi Wang                .contacts_deleted_toast, contactIds.length);
1106687d218928bd188a13cc32a3c553590fe0d564b1Wenyi Wang        mMainHandler.post(new Runnable() {
1107687d218928bd188a13cc32a3c553590fe0d564b1Wenyi Wang            @Override
1108687d218928bd188a13cc32a3c553590fe0d564b1Wenyi Wang            public void run() {
1109687d218928bd188a13cc32a3c553590fe0d564b1Wenyi Wang                Toast.makeText(ContactSaveService.this, deleteToastMessage, Toast.LENGTH_LONG)
1110687d218928bd188a13cc32a3c553590fe0d564b1Wenyi Wang                        .show();
1111687d218928bd188a13cc32a3c553590fe0d564b1Wenyi Wang            }
1112687d218928bd188a13cc32a3c553590fe0d564b1Wenyi Wang        });
1113d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell    }
1114d2962a3bb669a381d31a586df3b906033a8fa571Brian Attwell
11152b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    /**
11162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     * Creates an intent that can be sent to this service to join two contacts.
1117d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell     * The resulting contact uses the name from {@param contactId1} if possible.
11182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     */
11192b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static Intent createJoinContactsIntent(Context context, long contactId1,
1120d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            long contactId2, Class<? extends Activity> callbackActivity, String callbackAction) {
11212b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
11222b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS);
11232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1);
11242b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2);
11252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
11262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Callback intent will be invoked by the service once the contacts are joined.
11272b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
11282b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        callbackIntent.setAction(callbackAction);
11292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
11302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
11312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        return serviceIntent;
11322b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
11332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1134d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    /**
1135d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell     * Creates an intent to join all raw contacts inside {@param contactIds}'s contacts.
1136d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell     * No special attention is paid to where the resulting contact's name is taken from.
1137d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell     */
1138d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    public static Intent createJoinSeveralContactsIntent(Context context, long[] contactIds) {
1139d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        Intent serviceIntent = new Intent(context, ContactSaveService.class);
1140d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        serviceIntent.setAction(ContactSaveService.ACTION_JOIN_SEVERAL_CONTACTS);
1141d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_IDS, contactIds);
1142d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        return serviceIntent;
1143d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    }
1144d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
11452b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
11462b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    private interface JoinContactQuery {
11472b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        String[] PROJECTION = {
11482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts._ID,
11492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts.CONTACT_ID,
11502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts.DISPLAY_NAME_SOURCE,
11512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        };
11522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
11532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int _ID = 0;
11542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int CONTACT_ID = 1;
1155548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        int DISPLAY_NAME_SOURCE = 2;
1156548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell    }
1157548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell
1158548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell    private interface ContactEntityQuery {
1159548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        String[] PROJECTION = {
1160548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                Contacts.Entity.DATA_ID,
1161548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                Contacts.Entity.CONTACT_ID,
1162548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                Contacts.Entity.IS_SUPER_PRIMARY,
1163548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        };
1164548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        String SELECTION = Data.MIMETYPE + " = '" + StructuredName.CONTENT_ITEM_TYPE + "'" +
1165548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                " AND " + StructuredName.DISPLAY_NAME + "=" + Contacts.DISPLAY_NAME +
1166548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                " AND " + StructuredName.DISPLAY_NAME + " IS NOT NULL " +
1167548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                " AND " + StructuredName.DISPLAY_NAME + " != '' ";
1168548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell
1169548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        int DATA_ID = 0;
1170548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        int CONTACT_ID = 1;
1171548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        int IS_SUPER_PRIMARY = 2;
11722b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
11732b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1174d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    private void joinSeveralContacts(Intent intent) {
1175d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        final long[] contactIds = intent.getLongArrayExtra(EXTRA_CONTACT_IDS);
1176548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell
1177d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        // Load raw contact IDs for all contacts involved.
1178d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        long rawContactIds[] = getRawContactIdsForAggregation(contactIds);
1179d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        if (rawContactIds == null) {
1180d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            Log.e(TAG, "Invalid arguments for joinSeveralContacts request");
11812b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            return;
11822b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
11832b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1184d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        // For each pair of raw contacts, insert an aggregation exception
11852b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        final ContentResolver resolver = getContentResolver();
11860653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang        // The maximum number of operations per batch (aka yield point) is 500. See b/22480225
11870653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang        final int batchSize = MAX_CONTACTS_PROVIDER_BATCH_SIZE;
11880653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang        final ArrayList<ContentProviderOperation> operations = new ArrayList<>(batchSize);
1189d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        for (int i = 0; i < rawContactIds.length; i++) {
1190d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            for (int j = 0; j < rawContactIds.length; j++) {
1191d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                if (i != j) {
1192d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                    buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
1193d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                }
11940653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                // Before we get to 500 we need to flush the operations list
11950653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                if (operations.size() > 0 && operations.size() % batchSize == 0) {
11960653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                    if (!applyJoinOperations(resolver, operations)) {
11970653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                        return;
11980653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                    }
11990653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                    operations.clear();
12000653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang                }
1201d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            }
1202d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        }
12030653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang        if (operations.size() > 0 && !applyJoinOperations(resolver, operations)) {
12040653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang            return;
12050653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang        }
12060653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang        showToast(R.string.contactsJoinedMessage);
12070653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang    }
1208d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
12090653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang    /** Returns true if the batch was successfully applied and false otherwise. */
12100653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang    private boolean applyJoinOperations(ContentResolver resolver,
12110653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang            ArrayList<ContentProviderOperation> operations) {
1212d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        try {
1213d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            resolver.applyBatch(ContactsContract.AUTHORITY, operations);
12140653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang            return true;
1215d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        } catch (RemoteException | OperationApplicationException e) {
1216d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            Log.e(TAG, "Failed to apply aggregation exception batch", e);
1217d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            showToast(R.string.contactSavedErrorToast);
12180653de3dbac5b4f4a41332f09a2275a96dab4c8eWalter Jang            return false;
1219d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        }
1220d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    }
1221d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
1222d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
1223d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    private void joinContacts(Intent intent) {
1224d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1);
1225d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1);
12262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
12272b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Load raw contact IDs for all raw contacts involved - currently edited and selected
1228548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        // in the join UIs.
1229548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        long rawContactIds[] = getRawContactIdsForAggregation(contactId1, contactId2);
1230548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        if (rawContactIds == null) {
1231d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            Log.e(TAG, "Invalid arguments for joinContacts request");
1232c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner            return;
1233c12a280b496e6a997ab972641fb8e50e1eb8736cJay Shrauner        }
12342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1235548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
12362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
12372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // For each pair of raw contacts, insert an aggregation exception
12382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        for (int i = 0; i < rawContactIds.length; i++) {
12392b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            for (int j = 0; j < rawContactIds.length; j++) {
12402b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                if (i != j) {
12412b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
12422b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                }
12432b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            }
12442b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
12452b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1246d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        final ContentResolver resolver = getContentResolver();
1247d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
1248548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        // Use the name for contactId1 as the name for the newly aggregated contact.
1249548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        final Uri contactId1Uri = ContentUris.withAppendedId(
1250548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                Contacts.CONTENT_URI, contactId1);
1251548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        final Uri entityUri = Uri.withAppendedPath(
1252548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                contactId1Uri, Contacts.Entity.CONTENT_DIRECTORY);
1253548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        Cursor c = resolver.query(entityUri,
1254548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                ContactEntityQuery.PROJECTION, ContactEntityQuery.SELECTION, null, null);
1255548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        if (c == null) {
1256548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            Log.e(TAG, "Unable to open Contacts DB cursor");
1257548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            showToast(R.string.contactSavedErrorToast);
1258548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            return;
1259548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        }
1260548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        long dataIdToAddSuperPrimary = -1;
1261548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        try {
1262548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            if (c.moveToFirst()) {
1263548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                dataIdToAddSuperPrimary = c.getLong(ContactEntityQuery.DATA_ID);
1264548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            }
1265548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        } finally {
1266548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            c.close();
1267548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        }
1268548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell
1269548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        // Mark the name from contactId1 IS_SUPER_PRIMARY to make sure that the contact
1270548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        // display name does not change as a result of the join.
1271548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        if (dataIdToAddSuperPrimary != -1) {
12722b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Builder builder = ContentProviderOperation.newUpdate(
1273548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                    ContentUris.withAppendedId(Data.CONTENT_URI, dataIdToAddSuperPrimary));
1274548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            builder.withValue(Data.IS_SUPER_PRIMARY, 1);
1275548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            builder.withValue(Data.IS_PRIMARY, 1);
12762b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            operations.add(builder.build());
12772b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
12782b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
12792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        boolean success = false;
12802b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Apply all aggregation exceptions as one batch
12812b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        try {
12822b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            resolver.applyBatch(ContactsContract.AUTHORITY, operations);
1283886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            showToast(R.string.contactsJoinedMessage);
12842b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            success = true;
1285d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        } catch (RemoteException | OperationApplicationException e) {
12862b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Log.e(TAG, "Failed to apply aggregation exception batch", e);
1287886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            showToast(R.string.contactSavedErrorToast);
12882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
12892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
12902b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
12912b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        if (success) {
12922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Uri uri = RawContacts.getContactLookupUri(resolver,
12932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
12942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            callbackIntent.setData(uri);
12952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
12963a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
12972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
12982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
1299d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    private long[] getRawContactIdsForAggregation(long[] contactIds) {
1300d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        if (contactIds == null) {
1301d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            return null;
1302d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        }
1303d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
1304548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        final ContentResolver resolver = getContentResolver();
1305548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        long rawContactIds[];
1306d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
1307d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        final StringBuilder queryBuilder = new StringBuilder();
1308d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        final String stringContactIds[] = new String[contactIds.length];
1309d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        for (int i = 0; i < contactIds.length; i++) {
1310d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            queryBuilder.append(RawContacts.CONTACT_ID + "=?");
1311d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            stringContactIds[i] = String.valueOf(contactIds[i]);
1312d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            if (contactIds[i] == -1) {
1313d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                return null;
1314d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            }
1315d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            if (i == contactIds.length -1) {
1316d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                break;
1317d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            }
1318d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell            queryBuilder.append(" OR ");
1319d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        }
1320d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
1321548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        final Cursor c = resolver.query(RawContacts.CONTENT_URI,
1322548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                JoinContactQuery.PROJECTION,
1323d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                queryBuilder.toString(),
1324d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                stringContactIds, null);
1325548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        if (c == null) {
1326548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            Log.e(TAG, "Unable to open Contacts DB cursor");
1327548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            showToast(R.string.contactSavedErrorToast);
1328548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            return null;
1329548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        }
1330548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        try {
1331548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            if (c.getCount() < 2) {
1332d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell                Log.e(TAG, "Not enough raw contacts to aggregate together.");
1333548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                return null;
1334548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            }
1335548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            rawContactIds = new long[c.getCount()];
1336548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            for (int i = 0; i < rawContactIds.length; i++) {
1337548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                c.moveToPosition(i);
1338548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                long rawContactId = c.getLong(JoinContactQuery._ID);
1339548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell                rawContactIds[i] = rawContactId;
1340548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            }
1341548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        } finally {
1342548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell            c.close();
1343548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        }
1344548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell        return rawContactIds;
1345548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell    }
1346548f5c658547041feb50cdfbe6f48e900558c00cBrian Attwell
1347d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    private long[] getRawContactIdsForAggregation(long contactId1, long contactId2) {
1348d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell        return getRawContactIdsForAggregation(new long[] {contactId1, contactId2});
1349d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell    }
1350d3946cae17273ed1c2fceb507990882e3f828ba9Brian Attwell
13512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    /**
13522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation.
13532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     */
13542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations,
13552b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            long rawContactId1, long rawContactId2) {
13562b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Builder builder =
13572b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
13582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
13592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
13602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
13612b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        operations.add(builder.build());
13622b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
1363886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov
1364886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    /**
1365886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov     * Shows a toast on the UI thread.
1366886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov     */
1367886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    private void showToast(final int message) {
13683a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler.post(new Runnable() {
1369886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov
1370886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            @Override
1371886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            public void run() {
1372886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov                Toast.makeText(ContactSaveService.this, message, Toast.LENGTH_LONG).show();
1373886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            }
1374886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov        });
1375886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    }
13763a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
13773a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    private void deliverCallback(final Intent callbackIntent) {
13783a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler.post(new Runnable() {
13793a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
13803a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            @Override
13813a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            public void run() {
13823a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                deliverCallbackOnUiThread(callbackIntent);
13833a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            }
13843a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        });
13853a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
13863a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
13873a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    void deliverCallbackOnUiThread(final Intent callbackIntent) {
13883a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // TODO: this assumes that if there are multiple instances of the same
13893a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // activity registered, the last one registered is the one waiting for
13903a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // the callback. Validity of this assumption needs to be verified.
1391a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson        for (Listener listener : sListeners) {
1392a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson            if (callbackIntent.getComponent().equals(
1393a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson                    ((Activity) listener).getIntent().getComponent())) {
1394a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson                listener.onServiceCompleted(callbackIntent);
1395a831c0b539cdc120655856074d4621e8e60a843bHugo Hudson                return;
13963a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            }
13973a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        }
13983a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1399173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann}
1400