ContactSaveService.java revision 683b57e1fbf27c81c9973de814fc8bb1852e6df8
1173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann/*
2173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * Copyright (C) 2010 The Android Open Source Project
3173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann *
4173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * Licensed under the Apache License, Version 2.0 (the "License");
5173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * you may not use this file except in compliance with the License.
6173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * You may obtain a copy of the License at
7173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann *
8173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann *      http://www.apache.org/licenses/LICENSE-2.0
9173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann *
10173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * Unless required by applicable law or agreed to in writing, software
11173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * distributed under the License is distributed on an "AS IS" BASIS,
12173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * See the License for the specific language governing permissions and
14173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann * limitations under the License.
15173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann */
16173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
1718ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikovpackage com.android.contacts;
18173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
19a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport com.android.contacts.model.AccountTypeManager;
20a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport com.android.contacts.model.EntityDeltaList;
21a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport com.android.contacts.model.EntityModifier;
221ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikovimport com.google.android.collect.Lists;
23caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport com.google.android.collect.Sets;
24caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
25caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.accounts.Account;
263a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikovimport android.app.Activity;
27173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.app.IntentService;
28173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.content.ContentProviderOperation;
292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.content.ContentProviderOperation.Builder;
30caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentProviderResult;
31caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentResolver;
32e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikovimport android.content.ContentUris;
33caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.content.ContentValues;
349d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikovimport android.content.Context;
35173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.content.Intent;
362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.content.OperationApplicationException;
372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.database.Cursor;
38caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.net.Uri;
39886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikovimport android.os.Handler;
40886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikovimport android.os.Looper;
41a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikovimport android.os.Parcelable;
422b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.os.RemoteException;
43173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.provider.ContactsContract;
442b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.provider.ContactsContract.AggregationExceptions;
451ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.GroupMembership;
469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikovimport android.provider.ContactsContract.Contacts;
47caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.Data;
48e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikovimport android.provider.ContactsContract.Groups;
49caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport android.provider.ContactsContract.RawContacts;
50173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport android.util.Log;
512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikovimport android.widget.Toast;
52173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
53173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannimport java.util.ArrayList;
54caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport java.util.HashSet;
553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikovimport java.util.LinkedList;
56caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikovimport java.util.List;
57173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
5818ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov/**
5918ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov * A service responsible for saving changes to the content provider.
6018ffaa2561cc7dd2e3ef81737e6537931c0a9a11Dmitri Plotnikov */
61173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmannpublic class ContactSaveService extends IntentService {
62173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    private static final String TAG = "ContactSaveService";
63173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
64a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan    /** Set to true in order to view logs on content provider operations */
65a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan    private static final boolean DEBUG = false;
66a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan
67caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String ACTION_NEW_RAW_CONTACT = "newRawContact";
68caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
69caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_ACCOUNT_NAME = "accountName";
70caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_ACCOUNT_TYPE = "accountType";
71caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_CONTENT_VALUES = "contentValues";
72caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    public static final String EXTRA_CALLBACK_INTENT = "callbackIntent";
73caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
74a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String ACTION_SAVE_CONTACT = "saveContact";
75a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String EXTRA_CONTACT_STATE = "state";
76a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static final String EXTRA_SAVE_MODE = "saveMode";
77173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
781ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    public static final String ACTION_CREATE_GROUP = "createGroup";
79e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String ACTION_RENAME_GROUP = "renameGroup";
80e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String ACTION_DELETE_GROUP = "deleteGroup";
812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String ACTION_UPDATE_GROUP = "updateGroup";
82e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String EXTRA_GROUP_ID = "groupId";
83e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    public static final String EXTRA_GROUP_LABEL = "groupLabel";
842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String EXTRA_RAW_CONTACTS_TO_ADD = "rawContactsToAdd";
852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static final String EXTRA_RAW_CONTACTS_TO_REMOVE = "rawContactsToRemove";
86e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
879d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String ACTION_SET_STARRED = "setStarred";
887d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    public static final String ACTION_DELETE_CONTACT = "delete";
899d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String EXTRA_CONTACT_URI = "contactUri";
909d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static final String EXTRA_STARRED_FLAG = "starred";
919d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
920f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary";
930f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String ACTION_CLEAR_PRIMARY = "clearPrimary";
940f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static final String EXTRA_DATA_ID = "dataId";
950f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String ACTION_JOIN_CONTACTS = "joinContacts";
972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String EXTRA_CONTACT_ID1 = "contactId1";
982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String EXTRA_CONTACT_ID2 = "contactId2";
992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static final String EXTRA_CONTACT_WRITABLE = "contactWritable";
1002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
101683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String ACTION_SET_SEND_TO_VOICEMAIL = "sendToVoicemail";
102683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String EXTRA_SEND_TO_VOICEMAIL_FLAG = "sendToVoicemailFlag";
103683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
104683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String ACTION_SET_RINGTONE = "setRingtone";
105683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static final String EXTRA_CUSTOM_RINGTONE = "customRingtone";
106683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
107caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet(
108caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.MIMETYPE,
109caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.IS_PRIMARY,
110caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA1,
111caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA2,
112caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA3,
113caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA4,
114caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA5,
115caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA6,
116caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA7,
117caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA8,
118caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA9,
119caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA10,
120caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA11,
121caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA12,
122caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA13,
123caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA14,
124caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Data.DATA15
125caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    );
126caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
127a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    private static final int PERSIST_TRIES = 3;
128a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
1293a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public interface Listener {
1303a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        public void onServiceCompleted(Intent callbackIntent);
1313a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1323a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1333a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    private static final LinkedList<Listener> sListeners = new LinkedList<Listener>();
1343a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1353a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    private Handler mMainHandler;
1363a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
137173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    public ContactSaveService() {
138173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann        super(TAG);
139173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann        setIntentRedelivery(true);
1403a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler = new Handler(Looper.getMainLooper());
1413a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1423a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1433a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public static void registerListener(Listener listener) {
1443a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        if (!(listener instanceof Activity)) {
1453a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            throw new ClassCastException("Only activities can be registered to"
1463a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                    + " receive callback from " + ContactSaveService.class.getName());
1473a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        }
1483a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        synchronized (sListeners) {
1493a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            sListeners.addFirst(listener);
1503a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        }
1513a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
1523a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
1533a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    public static void unregisterListener(Listener listener) {
1543a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        synchronized (sListeners) {
1553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            sListeners.remove(listener);
1563a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        }
157173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    }
158173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann
159173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    @Override
160a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public Object getSystemService(String name) {
161a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Object service = super.getSystemService(name);
162a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        if (service != null) {
163a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            return service;
164a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
165a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
166a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return getApplicationContext().getSystemService(name);
167a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
168a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
169a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    @Override
170173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann    protected void onHandleIntent(Intent intent) {
171caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String action = intent.getAction();
172caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        if (ACTION_NEW_RAW_CONTACT.equals(action)) {
173caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            createRawContact(intent);
174a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        } else if (ACTION_SAVE_CONTACT.equals(action)) {
175a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            saveContact(intent);
1761ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        } else if (ACTION_CREATE_GROUP.equals(action)) {
1771ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov            createGroup(intent);
178e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        } else if (ACTION_RENAME_GROUP.equals(action)) {
179e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            renameGroup(intent);
180e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        } else if (ACTION_DELETE_GROUP.equals(action)) {
181e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            deleteGroup(intent);
1822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        } else if (ACTION_UPDATE_GROUP.equals(action)) {
1832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            updateGroup(intent);
1849d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        } else if (ACTION_SET_STARRED.equals(action)) {
1859d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            setStarred(intent);
1860f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) {
1870f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            setSuperPrimary(intent);
1880f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        } else if (ACTION_CLEAR_PRIMARY.equals(action)) {
1890f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            clearPrimary(intent);
1907d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        } else if (ACTION_DELETE_CONTACT.equals(action)) {
1917d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            deleteContact(intent);
1922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } else if (ACTION_JOIN_CONTACTS.equals(action)) {
1932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            joinContacts(intent);
194683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) {
195683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            setSendToVoicemail(intent);
196683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        } else if (ACTION_SET_RINGTONE.equals(action)) {
197683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            setRingtone(intent);
198caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
199caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
200caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
2019d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    /**
2029d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to create a new raw contact
2039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * using data presented as a set of ContentValues.
2049d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     */
2059d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createNewRawContactIntent(Context context,
2069d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            ArrayList<ContentValues> values, Account account, Class<?> callbackActivity,
2079d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            String callbackAction) {
2089d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(
2099d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov                context, ContactSaveService.class);
2109d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT);
2119d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        if (account != null) {
2129d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
2139d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
2149d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        }
2159d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putParcelableArrayListExtra(
2169d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov                ContactSaveService.EXTRA_CONTENT_VALUES, values);
2179d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
2189d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // Callback intent will be invoked by the service once the new contact is
2199d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // created.  The service will put the URI of the new contact as "data" on
2209d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // the callback intent.
2219d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
2229d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        callbackIntent.setAction(callbackAction);
2239d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
2249d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        return serviceIntent;
2259d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    }
2269d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
227caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    private void createRawContact(Intent intent) {
228caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
229caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
230caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES);
231caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
232caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
233caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
234caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
235caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .withValue(RawContacts.ACCOUNT_NAME, accountName)
236caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .withValue(RawContacts.ACCOUNT_TYPE, accountType)
237caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                .build());
238caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
239caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        int size = valueList.size();
240caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        for (int i = 0; i < size; i++) {
241caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            ContentValues values = valueList.get(i);
242caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            values.keySet().retainAll(ALLOWED_DATA_COLUMNS);
243caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
244caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .withValueBackReference(Data.RAW_CONTACT_ID, 0)
245caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .withValues(values)
246caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov                    .build());
247caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
248caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
249caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ContentResolver resolver = getContentResolver();
250caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        ContentProviderResult[] results;
251caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        try {
252caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            results = resolver.applyBatch(ContactsContract.AUTHORITY, operations);
253caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        } catch (Exception e) {
254caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov            throw new RuntimeException("Failed to store new contact", e);
255caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        }
256caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
257caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        Uri rawContactUri = results[0].uri;
258caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri));
259caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
2603a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
261caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
262caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
263caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    /**
264a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     * Creates an intent that can be sent to this service to create a new raw contact
265a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     * using data presented as a set of ContentValues.
266a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov     */
267a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    public static Intent createSaveContactIntent(Context context, EntityDeltaList state,
268a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            String saveModeExtraKey, int saveMode, Class<?> callbackActivity,
269a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            String callbackAction) {
270a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Intent serviceIntent = new Intent(
271a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                context, ContactSaveService.class);
272a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT);
273a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state);
274a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
275a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // Callback intent will be invoked by the service once the contact is
276a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // saved.  The service will put the URI of the new contact as "data" on
277a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // the callback intent.
278a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
279a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        callbackIntent.putExtra(saveModeExtraKey, saveMode);
280a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        callbackIntent.setAction(callbackAction);
281a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
282a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return serviceIntent;
283a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
284a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
285a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    private void saveContact(Intent intent) {
286a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        EntityDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE);
287a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
288a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
289a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // Trim any empty fields, and RawContacts, before persisting
290a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
291a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        EntityModifier.trimEmpty(state, accountTypes);
292a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
293a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        Uri lookupUri = null;
294a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
295a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final ContentResolver resolver = getContentResolver();
296a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
297a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        // Attempt to persist changes
298a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        int tries = 0;
299a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        while (tries++ < PERSIST_TRIES) {
300a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            try {
301a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Build operations and try applying
302a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final ArrayList<ContentProviderOperation> diff = state.buildDiff();
303a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                if (DEBUG) {
304a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    Log.v(TAG, "Content Provider Operations:");
305a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    for (ContentProviderOperation operation : diff) {
306a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                        Log.v(TAG, operation.toString());
307a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                    }
308a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan                }
309a007e442687d3836d6a9f0d0ddcea527fa1481acKatherine Kuan
310a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                ContentProviderResult[] results = null;
311a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (!diff.isEmpty()) {
312a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
313a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
314a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
315a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final long rawContactId = getRawContactId(state, diff, results);
316a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (rawContactId == -1) {
317a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    throw new IllegalStateException("Could not determine RawContact ID after save");
318a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
319a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final Uri rawContactUri = ContentUris.withAppendedId(
320a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        RawContacts.CONTENT_URI, rawContactId);
321a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri);
322a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                Log.v(TAG, "Saved contact. New URI: " + lookupUri);
323a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                break;
324a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
325a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            } catch (RemoteException e) {
326a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Something went wrong, bail without success
327a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                Log.e(TAG, "Problem persisting user edits", e);
328a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                break;
329a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
330a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            } catch (OperationApplicationException e) {
331a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                // Version consistency failed, re-parent change and try again
332a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString());
333a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN(");
334a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                boolean first = true;
335a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final int count = state.size();
336a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                for (int i = 0; i < count; i++) {
337a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    Long rawContactId = state.getRawContactId(i);
338a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    if (rawContactId != null && rawContactId != -1) {
339a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        if (!first) {
340a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                            sb.append(',');
341a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        }
342a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        sb.append(rawContactId);
343a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        first = false;
344a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    }
345a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
346a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                sb.append(")");
347a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
348a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                if (first) {
349a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    throw new IllegalStateException("Version consistency failed for a new contact");
350a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                }
351a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
352a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                final EntityDeltaList newState = EntityDeltaList.fromQuery(resolver,
353a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                        sb.toString(), null, null);
354a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                state = EntityDeltaList.mergeAfter(newState, state);
355a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            }
356a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
357a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
358a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        callbackIntent.setData(lookupUri);
359a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
3603a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
361a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
362a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
363a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    private long getRawContactId(EntityDeltaList state,
364a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            final ArrayList<ContentProviderOperation> diff,
365a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            final ContentProviderResult[] results) {
366a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        long rawContactId = state.findRawContactId();
367a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        if (rawContactId != -1) {
368a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            return rawContactId;
369a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
370a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
371a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        final int diffSize = diff.size();
372a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        for (int i = 0; i < diffSize; i++) {
373a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            ContentProviderOperation operation = diff.get(i);
374a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            if (operation.getType() == ContentProviderOperation.TYPE_INSERT
375a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                    && operation.getUri().getEncodedPath().contains(
376a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                            RawContacts.CONTENT_URI.getEncodedPath())) {
377a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov                return ContentUris.parseId(results[i].uri);
378a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov            }
379a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        }
380a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov        return -1;
381a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    }
382a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov
383a011414b12955a91c8f3efe528f374654d930098Dmitri Plotnikov    /**
384717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * Creates an intent that can be sent to this service to create a new group as
385717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * well as add new members at the same time.
386717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     *
387717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param context of the application
388717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param account in which the group should be created
389717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param label is the name of the group (cannot be null)
390717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param rawContactsToAdd is an array of raw contact IDs for contacts that
391717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     *            should be added to the group
392717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param callbackActivity is the activity to send the callback intent to
393717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan     * @param callbackAction is the intent action for the callback intent
394caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov     */
395683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
396717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    public static Intent createNewGroupIntent(Context context, Account account,
397717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            String label, long[] rawContactsToAdd, Class<?> callbackActivity,
398717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            String callbackAction) {
3999d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
4009d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP);
4019d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
4029d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
4039d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label);
404717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
405caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov
4069d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        // Callback intent will be invoked by the service once the new group is
407717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // created.
4089d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
4099d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        callbackIntent.setAction(callbackAction);
4101ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
4119d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
4121ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        return serviceIntent;
4131ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    }
4141ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
4151ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    private void createGroup(Intent intent) {
416717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
417717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
418717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
419717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
4201ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
4211ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        ContentValues values = new ContentValues();
4221ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.ACCOUNT_TYPE, accountType);
4231ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.ACCOUNT_NAME, accountName);
4241ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Groups.TITLE, label);
4251ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
426717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final ContentResolver resolver = getContentResolver();
427717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
428717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Create the new group
429717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values);
430717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
431717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // If there's no URI, then the insertion failed. Abort early because group members can't be
432717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // added if the group doesn't exist
4331ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        if (groupUri == null) {
434717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            Log.e(TAG, "Couldn't create group with label " + label);
4351ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov            return;
4361ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        }
4371ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
438717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Add new group members
439717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri));
440717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
441717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // TODO: Move this into the contact editor where it belongs. This needs to be integrated
442717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // with the way other intent extras that are passed to the {@link ContactEditorActivity}.
4431ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.clear();
4441ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
4451ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri));
4461ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
4471ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
448c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setData(groupUri);
449717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // TODO: This can be taken out when the above TODO is addressed
4501ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov        callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values));
4513a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
4521ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    }
4531ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov
4541ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov    /**
4559d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to rename a group.
4561ac58b6f2a925c5a4f759346e5244dfd174acd08Dmitri Plotnikov     */
457c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan    public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel,
458c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan            Class<?> callbackActivity, String callbackAction) {
4599d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
4609d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP);
4619d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
4629d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
463c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
464c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        // Callback intent will be invoked by the service once the group is renamed.
465c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        Intent callbackIntent = new Intent(context, callbackActivity);
466c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setAction(callbackAction);
467c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
468c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
469caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov        return serviceIntent;
470caf0bc759c4ef96dde5bb0a5256c1dcb51b6ccc4Dmitri Plotnikov    }
471e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
472e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    private void renameGroup(Intent intent) {
473e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
474e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
475e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
476e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        if (groupId == -1) {
477e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            Log.e(TAG, "Invalid arguments for renameGroup request");
478e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            return;
479e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        }
480e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
481e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        ContentValues values = new ContentValues();
482e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        values.put(Groups.TITLE, label);
483c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
484c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        getContentResolver().update(groupUri, values, null, null);
485c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan
486c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
487c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        callbackIntent.setData(groupUri);
488c6b8afe730255537978f2c938cca6986cae63c34Katherine Kuan        deliverCallback(callbackIntent);
489e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
490e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
491e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    /**
4929d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to delete a group.
493e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov     */
4949d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createGroupDeletionIntent(Context context, long groupId) {
4959d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
4969d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP);
497e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
498e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        return serviceIntent;
499e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
500e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
501e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    private void deleteGroup(Intent intent) {
502e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
503e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        if (groupId == -1) {
504e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            Log.e(TAG, "Invalid arguments for deleteGroup request");
505e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov            return;
506e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        }
507e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
508e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        getContentResolver().delete(
509e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov                ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null);
510e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
511e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov
512e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    /**
5132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * Creates an intent that can be sent to this service to rename a group as
5142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * well as add and remove members from the group.
5152d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *
5162d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param context of the application
5172d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param groupId of the group that should be modified
5182d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param newLabel is the updated name of the group (can be null if the name
5192d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            should not be updated)
5202d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param rawContactsToAdd is an array of raw contact IDs for contacts that
5212d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            should be added to the group
5222d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param rawContactsToRemove is an array of raw contact IDs for contacts
5232d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     *            that should be removed from the group
5242d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param callbackActivity is the activity to send the callback intent to
5252d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     * @param callbackAction is the intent action for the callback intent
5262d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan     */
5272d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel,
5282d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            long[] rawContactsToAdd, long[] rawContactsToRemove,
5292d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            Class<?> callbackActivity, String callbackAction) {
5302d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        Intent serviceIntent = new Intent(context, ContactSaveService.class);
5312d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP);
5322d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
5332d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
5342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
5352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_REMOVE,
5362d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactsToRemove);
5372d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5382d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        // Callback intent will be invoked by the service once the group is updated
5392d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        Intent callbackIntent = new Intent(context, callbackActivity);
5402d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        callbackIntent.setAction(callbackAction);
5412d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
5422d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5432d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        return serviceIntent;
5442d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    }
5452d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5462d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    private void updateGroup(Intent intent) {
5472d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
5482d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
5492d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
5502d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        long[] rawContactsToRemove = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_REMOVE);
5512d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5522d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        if (groupId == -1) {
5532d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            Log.e(TAG, "Invalid arguments for updateGroup request");
5542d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            return;
5552d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
5562d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5572d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        final ContentResolver resolver = getContentResolver();
5582d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
5592d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5602d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        // Update group name if necessary
5612d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        if (label != null) {
5622d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            ContentValues values = new ContentValues();
5632d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            values.put(Groups.TITLE, label);
564717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            resolver.update(groupUri, values, null, null);
5652d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
5662d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
567717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        // Add and remove members if necessary
568717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        addMembersToGroup(resolver, rawContactsToAdd, groupId);
569717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        removeMembersFromGroup(resolver, rawContactsToRemove, groupId);
570717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
571717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
572717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        callbackIntent.setData(groupUri);
573717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        deliverCallback(callbackIntent);
574717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    }
575717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan
576717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    private void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd,
577717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            long groupId) {
578717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        if (rawContactsToAdd == null) {
579717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            return;
580717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        }
5812d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        for (long rawContactId : rawContactsToAdd) {
5822d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            try {
5832d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ArrayList<ContentProviderOperation> rawContactOperations =
5842d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        new ArrayList<ContentProviderOperation>();
5852d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5862d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Build an assert operation to ensure the contact is not already in the group
5872d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ContentProviderOperation.Builder assertBuilder = ContentProviderOperation
5882d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        .newAssertQuery(Data.CONTENT_URI);
5892d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                assertBuilder.withSelection(Data.RAW_CONTACT_ID + "=? AND " +
5902d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
5912d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        new String[] { String.valueOf(rawContactId),
5922d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
5932d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                assertBuilder.withExpectedCount(0);
5942d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactOperations.add(assertBuilder.build());
5952d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
5962d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Build an insert operation to add the contact to the group
5972d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                final ContentProviderOperation.Builder insertBuilder = ContentProviderOperation
5982d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        .newInsert(Data.CONTENT_URI);
5992d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(Data.RAW_CONTACT_ID, rawContactId);
6002d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
6012d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                insertBuilder.withValue(GroupMembership.GROUP_ROW_ID, groupId);
6022d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                rawContactOperations.add(insertBuilder.build());
6032d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
6042d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                if (DEBUG) {
6052d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    for (ContentProviderOperation operation : rawContactOperations) {
6062d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        Log.v(TAG, operation.toString());
6072d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    }
6082d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                }
6092d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
6102d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Apply batch
6112d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                ContentProviderResult[] results = null;
6122d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                if (!rawContactOperations.isEmpty()) {
6132d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    results = resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations);
6142d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                }
6152d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            } catch (RemoteException e) {
6162d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // Something went wrong, bail without success
6172d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                Log.e(TAG, "Problem persisting user edits for raw contact ID " +
6182d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(rawContactId), e);
6192d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            } catch (OperationApplicationException e) {
6202d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // The assert could have failed because the contact is already in the group,
6212d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                // just continue to the next contact
6222d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                Log.w(TAG, "Assert failed in adding raw contact ID " +
6232d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(rawContactId) + ". Already exists in group " +
6242d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                        String.valueOf(groupId), e);
6252d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            }
6262d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
627717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    }
6282d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
629717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan    private void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove,
630717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            long groupId) {
631717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        if (rawContactsToRemove == null) {
632717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan            return;
633717e343811088da922cd84fb0d196de85fba7fe9Katherine Kuan        }
6342d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        for (long rawContactId : rawContactsToRemove) {
6352d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // Apply the delete operation on the data row for the given raw contact's
6362d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // membership in the given group. If no contact matches the provided selection, then
6372d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            // nothing will be done. Just continue to the next contact.
6382d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan            getContentResolver().delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " +
6392d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
6402d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    new String[] { String.valueOf(rawContactId),
6412d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan                    GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
6422d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan        }
6432d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    }
6442d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan
6452d851cc895ffc7afd322298c7d4391ca5bea1a2dKatherine Kuan    /**
6469d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov     * Creates an intent that can be sent to this service to star or un-star a contact.
647e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov     */
6489d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) {
6499d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
6509d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED);
6519d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
6529d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value);
6539d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
654e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov        return serviceIntent;
655e898a9fa52728b2ff6fcd3add693471e9e15553dDmitri Plotnikov    }
6569d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
6579d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    private void setStarred(Intent intent) {
6589d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
6599d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false);
6609d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        if (contactUri == null) {
6619d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            Log.e(TAG, "Invalid arguments for setStarred request");
6629d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov            return;
6639d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        }
6649d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov
6659d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        final ContentValues values = new ContentValues(1);
6669d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        values.put(Contacts.STARRED, value);
6679d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov        getContentResolver().update(contactUri, values, null, null);
6689d730dd9d9efe125c9102b298f897577157ffecdDmitri Plotnikov    }
6690f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
6700f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    /**
671683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     * Creates an intent that can be sent to this service to set the redirect to voicemail.
672683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     */
673683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static Intent createSetSendToVoicemail(Context context, Uri contactUri,
674683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            boolean value) {
675683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Intent serviceIntent = new Intent(context, ContactSaveService.class);
676683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.setAction(ContactSaveService.ACTION_SET_SEND_TO_VOICEMAIL);
677683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
678683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_SEND_TO_VOICEMAIL_FLAG, value);
679683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
680683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        return serviceIntent;
681683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
682683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
683683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    private void setSendToVoicemail(Intent intent) {
684683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
685683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        boolean value = intent.getBooleanExtra(EXTRA_SEND_TO_VOICEMAIL_FLAG, false);
686683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        if (contactUri == null) {
687683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            Log.e(TAG, "Invalid arguments for setRedirectToVoicemail");
688683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            return;
689683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        }
690683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
691683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        final ContentValues values = new ContentValues(1);
692683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        values.put(Contacts.SEND_TO_VOICEMAIL, value);
693683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        getContentResolver().update(contactUri, values, null, null);
694683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
695683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
696683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    /**
697683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     * Creates an intent that can be sent to this service to save the contact's ringtone.
698683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson     */
699683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    public static Intent createSetRingtone(Context context, Uri contactUri,
700683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            String value) {
701683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Intent serviceIntent = new Intent(context, ContactSaveService.class);
702683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.setAction(ContactSaveService.ACTION_SET_RINGTONE);
703683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
704683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        serviceIntent.putExtra(ContactSaveService.EXTRA_CUSTOM_RINGTONE, value);
705683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
706683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        return serviceIntent;
707683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
708683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
709683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    private void setRingtone(Intent intent) {
710683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
711683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        String value = intent.getStringExtra(EXTRA_CUSTOM_RINGTONE);
712683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        if (contactUri == null) {
713683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            Log.e(TAG, "Invalid arguments for setRingtone");
714683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson            return;
715683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        }
716683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        ContentValues values = new ContentValues(1);
717683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        values.put(Contacts.CUSTOM_RINGTONE, value);
718683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson        getContentResolver().update(contactUri, values, null, null);
719683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    }
720683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson
721683b57e1fbf27c81c9973de814fc8bb1852e6df8Isaac Katzenelson    /**
7220f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * Creates an intent that sets the selected data item as super primary (default)
7230f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     */
7240f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static Intent createSetSuperPrimaryIntent(Context context, long dataId) {
7250f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        Intent serviceIntent = new Intent(context, ContactSaveService.class);
7260f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY);
7270f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
7280f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        return serviceIntent;
7290f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
7300f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
7310f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    private void setSuperPrimary(Intent intent) {
7320f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
7330f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        if (dataId == -1) {
7340f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            Log.e(TAG, "Invalid arguments for setSuperPrimary request");
7350f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            return;
7360f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        }
7370f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
7380f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        // Update the primary values in the data record.
7390f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        ContentValues values = new ContentValues(1);
7400f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_SUPER_PRIMARY, 1);
7410f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_PRIMARY, 1);
7420f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
7430f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
7440f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann                values, null, null);
7450f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
7460f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
7470f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    /**
7480f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * Creates an intent that clears the primary flag of all data items that belong to the same
7490f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * raw_contact as the given data item. Will only clear, if the data item was primary before
7500f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     * this call
7510f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann     */
7520f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    public static Intent createClearPrimaryIntent(Context context, long dataId) {
7530f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        Intent serviceIntent = new Intent(context, ContactSaveService.class);
7540f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY);
7550f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
7560f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        return serviceIntent;
7570f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
7580f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
7590f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    private void clearPrimary(Intent intent) {
7600f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
7610f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        if (dataId == -1) {
7620f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            Log.e(TAG, "Invalid arguments for clearPrimary request");
7630f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann            return;
7640f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        }
7650f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
7660f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        // Update the primary values in the data record.
7670f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        ContentValues values = new ContentValues(1);
7680f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_SUPER_PRIMARY, 0);
7690f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        values.put(Data.IS_PRIMARY, 0);
7700f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann
7710f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann        getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
7720f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann                values, null, null);
7730f78e8b198c56963d6d6044839bb0679f4afd075Daniel Lehmann    }
7747d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
7757d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    /**
7767d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov     * Creates an intent that can be sent to this service to delete a contact.
7777d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov     */
7787d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    public static Intent createDeleteContactIntent(Context context, Uri contactUri) {
7797d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
7807d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT);
7817d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
7827d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        return serviceIntent;
7837d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    }
7847d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
7857d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    private void deleteContact(Intent intent) {
7867d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
7877d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        if (contactUri == null) {
7887d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            Log.e(TAG, "Invalid arguments for deleteContact request");
7897d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov            return;
7907d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        }
7917d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov
7927d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov        getContentResolver().delete(contactUri, null, null);
7937d8cabb65b02096583b928c64ae455d1f79e5633Dmitri Plotnikov    }
7942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
7952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    /**
7962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     * Creates an intent that can be sent to this service to join two contacts.
7972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     */
7982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    public static Intent createJoinContactsIntent(Context context, long contactId1,
7992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            long contactId2, boolean contactWritable,
8002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Class<?> callbackActivity, String callbackAction) {
8012b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent serviceIntent = new Intent(context, ContactSaveService.class);
8022b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS);
8032b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1);
8042b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2);
8052b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_WRITABLE, contactWritable);
8062b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8072b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Callback intent will be invoked by the service once the contacts are joined.
8082b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent callbackIntent = new Intent(context, callbackActivity);
8092b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        callbackIntent.setAction(callbackAction);
8102b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
8112b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8122b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        return serviceIntent;
8132b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
8142b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8152b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    private interface JoinContactQuery {
8172b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        String[] PROJECTION = {
8182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts._ID,
8192b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts.CONTACT_ID,
8202b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts.NAME_VERIFIED,
8212b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                RawContacts.DISPLAY_NAME_SOURCE,
8222b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        };
8232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8242b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?";
8252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int _ID = 0;
8272b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int CONTACT_ID = 1;
8282b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int NAME_VERIFIED = 2;
8292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        int DISPLAY_NAME_SOURCE = 3;
8302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
8312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8322b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    private void joinContacts(Intent intent) {
8332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1);
8342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1);
8352b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        boolean writable = intent.getBooleanExtra(EXTRA_CONTACT_WRITABLE, false);
8362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        if (contactId1 == -1 || contactId2 == -1) {
8372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Log.e(TAG, "Invalid arguments for joinContacts request");
8382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            return;
8392b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
8402b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8412b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        final ContentResolver resolver = getContentResolver();
8422b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8432b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Load raw contact IDs for all raw contacts involved - currently edited and selected
8442b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // in the join UIs
8452b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Cursor c = resolver.query(RawContacts.CONTENT_URI,
8462b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                JoinContactQuery.PROJECTION,
8472b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                JoinContactQuery.SELECTION,
8482b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                new String[]{String.valueOf(contactId1), String.valueOf(contactId2)}, null);
8492b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8502b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        long rawContactIds[];
8512b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        long verifiedNameRawContactId = -1;
8522b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        try {
8532b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            int maxDisplayNameSource = -1;
8542b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            rawContactIds = new long[c.getCount()];
8552b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            for (int i = 0; i < rawContactIds.length; i++) {
8562b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                c.moveToPosition(i);
8572b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                long rawContactId = c.getLong(JoinContactQuery._ID);
8582b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                rawContactIds[i] = rawContactId;
8592b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
8602b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                if (nameSource > maxDisplayNameSource) {
8612b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    maxDisplayNameSource = nameSource;
8622b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                }
8632b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            }
8642b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8652b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            // Find an appropriate display name for the joined contact:
8662b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            // if should have a higher DisplayNameSource or be the name
8672b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            // of the original contact that we are joining with another.
8682b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            if (writable) {
8692b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                for (int i = 0; i < rawContactIds.length; i++) {
8702b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    c.moveToPosition(i);
8712b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    if (c.getLong(JoinContactQuery.CONTACT_ID) == contactId1) {
8722b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                        int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
8732b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                        if (nameSource == maxDisplayNameSource
8742b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                                && (verifiedNameRawContactId == -1
8752b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                                        || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0)) {
8762b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                            verifiedNameRawContactId = c.getLong(JoinContactQuery._ID);
8772b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                        }
8782b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    }
8792b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                }
8802b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            }
8812b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } finally {
8822b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            c.close();
8832b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
8842b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8852b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // For each pair of raw contacts, insert an aggregation exception
8862b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
8872b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        for (int i = 0; i < rawContactIds.length; i++) {
8882b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            for (int j = 0; j < rawContactIds.length; j++) {
8892b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                if (i != j) {
8902b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
8912b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                }
8922b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            }
8932b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
8942b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
8952b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Mark the original contact as "name verified" to make sure that the contact
8962b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // display name does not change as a result of the join
8972b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        if (verifiedNameRawContactId != -1) {
8982b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Builder builder = ContentProviderOperation.newUpdate(
8992b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId));
9002b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            builder.withValue(RawContacts.NAME_VERIFIED, 1);
9012b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            operations.add(builder.build());
9022b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
9032b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9042b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        boolean success = false;
9052b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        // Apply all aggregation exceptions as one batch
9062b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        try {
9072b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            resolver.applyBatch(ContactsContract.AUTHORITY, operations);
908886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            showToast(R.string.contactsJoinedMessage);
9092b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            success = true;
9102b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } catch (RemoteException e) {
9112b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Log.e(TAG, "Failed to apply aggregation exception batch", e);
912886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            showToast(R.string.contactSavedErrorToast);
9132b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        } catch (OperationApplicationException e) {
9142b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Log.e(TAG, "Failed to apply aggregation exception batch", e);
915886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            showToast(R.string.contactSavedErrorToast);
9162b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
9172b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9182b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
9192b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        if (success) {
9202b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            Uri uri = RawContacts.getContactLookupUri(resolver,
9212b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
9222b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            callbackIntent.setData(uri);
9232b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        }
9243a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        deliverCallback(callbackIntent);
9252b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
9262b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov
9272b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    /**
9282b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation.
9292b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov     */
9302b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations,
9312b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov            long rawContactId1, long rawContactId2) {
9322b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        Builder builder =
9332b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov                ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
9342b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
9352b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
9362b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
9372b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov        operations.add(builder.build());
9382b46f0301c51973d6e3b02b78b398af097244af9Dmitri Plotnikov    }
939886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov
940886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    /**
941886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov     * Shows a toast on the UI thread.
942886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov     */
943886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    private void showToast(final int message) {
9443a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler.post(new Runnable() {
945886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov
946886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            @Override
947886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            public void run() {
948886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov                Toast.makeText(ContactSaveService.this, message, Toast.LENGTH_LONG).show();
949886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov            }
950886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov        });
951886d3d6de4a6f7202b1871f5af9944e8650413dfDmitri Plotnikov    }
9523a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
9533a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    private void deliverCallback(final Intent callbackIntent) {
9543a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        mMainHandler.post(new Runnable() {
9553a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
9563a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            @Override
9573a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            public void run() {
9583a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                deliverCallbackOnUiThread(callbackIntent);
9593a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            }
9603a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        });
9613a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
9623a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov
9633a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    void deliverCallbackOnUiThread(final Intent callbackIntent) {
9643a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // TODO: this assumes that if there are multiple instances of the same
9653a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // activity registered, the last one registered is the one waiting for
9663a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        // the callback. Validity of this assumption needs to be verified.
9673a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        synchronized (sListeners) {
9683a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            for (Listener listener : sListeners) {
9693a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                if (callbackIntent.getComponent().equals(
9703a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                        ((Activity) listener).getIntent().getComponent())) {
9713a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                    listener.onServiceCompleted(callbackIntent);
9723a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                    return;
9733a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov                }
9743a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov            }
9753a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov        }
9763a6a905a39e6cbb3b2dc99835cdf28c84437691aDmitri Plotnikov    }
977173ffe1300afa6a88a7f0a924adade121e564274Daniel Lehmann}
978