blob: e2ab6b0b571990fd2201d1446bedcac8b6511c0f [file] [log] [blame]
Daniel Lehmann173ffe12010-06-14 18:19:10 -07001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080017package com.android.contacts;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070018
Dmitri Plotnikova0114142011-02-15 13:53:21 -080019import com.android.contacts.model.AccountTypeManager;
20import com.android.contacts.model.EntityDeltaList;
21import com.android.contacts.model.EntityModifier;
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -080022import com.google.android.collect.Lists;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070023import com.google.android.collect.Sets;
24
25import android.accounts.Account;
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -080026import android.app.Activity;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070027import android.app.IntentService;
28import android.content.ContentProviderOperation;
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080029import android.content.ContentProviderOperation.Builder;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070030import android.content.ContentProviderResult;
31import android.content.ContentResolver;
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -080032import android.content.ContentUris;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070033import android.content.ContentValues;
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -080034import android.content.Context;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070035import android.content.Intent;
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080036import android.content.OperationApplicationException;
37import android.database.Cursor;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070038import android.net.Uri;
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -080039import android.os.Handler;
40import android.os.Looper;
Dmitri Plotnikova0114142011-02-15 13:53:21 -080041import android.os.Parcelable;
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080042import android.os.RemoteException;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070043import android.provider.ContactsContract;
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080044import android.provider.ContactsContract.AggregationExceptions;
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -080045import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -080046import android.provider.ContactsContract.Contacts;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070047import android.provider.ContactsContract.Data;
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -080048import android.provider.ContactsContract.Groups;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070049import android.provider.ContactsContract.RawContacts;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070050import android.util.Log;
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080051import android.widget.Toast;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070052
53import java.util.ArrayList;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070054import java.util.HashSet;
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -080055import java.util.LinkedList;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070056import java.util.List;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070057
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080058/**
59 * A service responsible for saving changes to the content provider.
60 */
Daniel Lehmann173ffe12010-06-14 18:19:10 -070061public class ContactSaveService extends IntentService {
62 private static final String TAG = "ContactSaveService";
63
Katherine Kuana007e442011-07-07 09:25:34 -070064 /** Set to true in order to view logs on content provider operations */
65 private static final boolean DEBUG = false;
66
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070067 public static final String ACTION_NEW_RAW_CONTACT = "newRawContact";
68
69 public static final String EXTRA_ACCOUNT_NAME = "accountName";
70 public static final String EXTRA_ACCOUNT_TYPE = "accountType";
71 public static final String EXTRA_CONTENT_VALUES = "contentValues";
72 public static final String EXTRA_CALLBACK_INTENT = "callbackIntent";
73
Dmitri Plotnikova0114142011-02-15 13:53:21 -080074 public static final String ACTION_SAVE_CONTACT = "saveContact";
75 public static final String EXTRA_CONTACT_STATE = "state";
76 public static final String EXTRA_SAVE_MODE = "saveMode";
Daniel Lehmann173ffe12010-06-14 18:19:10 -070077
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -080078 public static final String ACTION_CREATE_GROUP = "createGroup";
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -080079 public static final String ACTION_RENAME_GROUP = "renameGroup";
80 public static final String ACTION_DELETE_GROUP = "deleteGroup";
Katherine Kuan2d851cc2011-07-05 16:23:27 -070081 public static final String ACTION_UPDATE_GROUP = "updateGroup";
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -080082 public static final String EXTRA_GROUP_ID = "groupId";
83 public static final String EXTRA_GROUP_LABEL = "groupLabel";
Katherine Kuan2d851cc2011-07-05 16:23:27 -070084 public static final String EXTRA_RAW_CONTACTS_TO_ADD = "rawContactsToAdd";
85 public static final String EXTRA_RAW_CONTACTS_TO_REMOVE = "rawContactsToRemove";
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -080086
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -080087 public static final String ACTION_SET_STARRED = "setStarred";
Dmitri Plotnikov7d8cabb2010-11-24 17:40:01 -080088 public static final String ACTION_DELETE_CONTACT = "delete";
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -080089 public static final String EXTRA_CONTACT_URI = "contactUri";
90 public static final String EXTRA_STARRED_FLAG = "starred";
91
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -080092 public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary";
93 public static final String ACTION_CLEAR_PRIMARY = "clearPrimary";
94 public static final String EXTRA_DATA_ID = "dataId";
95
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080096 public static final String ACTION_JOIN_CONTACTS = "joinContacts";
97 public static final String EXTRA_CONTACT_ID1 = "contactId1";
98 public static final String EXTRA_CONTACT_ID2 = "contactId2";
99 public static final String EXTRA_CONTACT_WRITABLE = "contactWritable";
100
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700101 public static final String ACTION_SET_SEND_TO_VOICEMAIL = "sendToVoicemail";
102 public static final String EXTRA_SEND_TO_VOICEMAIL_FLAG = "sendToVoicemailFlag";
103
104 public static final String ACTION_SET_RINGTONE = "setRingtone";
105 public static final String EXTRA_CUSTOM_RINGTONE = "customRingtone";
106
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700107 private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet(
108 Data.MIMETYPE,
109 Data.IS_PRIMARY,
110 Data.DATA1,
111 Data.DATA2,
112 Data.DATA3,
113 Data.DATA4,
114 Data.DATA5,
115 Data.DATA6,
116 Data.DATA7,
117 Data.DATA8,
118 Data.DATA9,
119 Data.DATA10,
120 Data.DATA11,
121 Data.DATA12,
122 Data.DATA13,
123 Data.DATA14,
124 Data.DATA15
125 );
126
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800127 private static final int PERSIST_TRIES = 3;
128
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800129 public interface Listener {
130 public void onServiceCompleted(Intent callbackIntent);
131 }
132
133 private static final LinkedList<Listener> sListeners = new LinkedList<Listener>();
134
135 private Handler mMainHandler;
136
Daniel Lehmann173ffe12010-06-14 18:19:10 -0700137 public ContactSaveService() {
138 super(TAG);
139 setIntentRedelivery(true);
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800140 mMainHandler = new Handler(Looper.getMainLooper());
141 }
142
143 public static void registerListener(Listener listener) {
144 if (!(listener instanceof Activity)) {
145 throw new ClassCastException("Only activities can be registered to"
146 + " receive callback from " + ContactSaveService.class.getName());
147 }
148 synchronized (sListeners) {
149 sListeners.addFirst(listener);
150 }
151 }
152
153 public static void unregisterListener(Listener listener) {
154 synchronized (sListeners) {
155 sListeners.remove(listener);
156 }
Daniel Lehmann173ffe12010-06-14 18:19:10 -0700157 }
158
159 @Override
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800160 public Object getSystemService(String name) {
161 Object service = super.getSystemService(name);
162 if (service != null) {
163 return service;
164 }
165
166 return getApplicationContext().getSystemService(name);
167 }
168
169 @Override
Daniel Lehmann173ffe12010-06-14 18:19:10 -0700170 protected void onHandleIntent(Intent intent) {
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700171 String action = intent.getAction();
172 if (ACTION_NEW_RAW_CONTACT.equals(action)) {
173 createRawContact(intent);
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800174 } else if (ACTION_SAVE_CONTACT.equals(action)) {
175 saveContact(intent);
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800176 } else if (ACTION_CREATE_GROUP.equals(action)) {
177 createGroup(intent);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800178 } else if (ACTION_RENAME_GROUP.equals(action)) {
179 renameGroup(intent);
180 } else if (ACTION_DELETE_GROUP.equals(action)) {
181 deleteGroup(intent);
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700182 } else if (ACTION_UPDATE_GROUP.equals(action)) {
183 updateGroup(intent);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800184 } else if (ACTION_SET_STARRED.equals(action)) {
185 setStarred(intent);
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -0800186 } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) {
187 setSuperPrimary(intent);
188 } else if (ACTION_CLEAR_PRIMARY.equals(action)) {
189 clearPrimary(intent);
Dmitri Plotnikov7d8cabb2010-11-24 17:40:01 -0800190 } else if (ACTION_DELETE_CONTACT.equals(action)) {
191 deleteContact(intent);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800192 } else if (ACTION_JOIN_CONTACTS.equals(action)) {
193 joinContacts(intent);
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700194 } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) {
195 setSendToVoicemail(intent);
196 } else if (ACTION_SET_RINGTONE.equals(action)) {
197 setRingtone(intent);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700198 }
199 }
200
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800201 /**
202 * Creates an intent that can be sent to this service to create a new raw contact
203 * using data presented as a set of ContentValues.
204 */
205 public static Intent createNewRawContactIntent(Context context,
206 ArrayList<ContentValues> values, Account account, Class<?> callbackActivity,
207 String callbackAction) {
208 Intent serviceIntent = new Intent(
209 context, ContactSaveService.class);
210 serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT);
211 if (account != null) {
212 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
213 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
214 }
215 serviceIntent.putParcelableArrayListExtra(
216 ContactSaveService.EXTRA_CONTENT_VALUES, values);
217
218 // Callback intent will be invoked by the service once the new contact is
219 // created. The service will put the URI of the new contact as "data" on
220 // the callback intent.
221 Intent callbackIntent = new Intent(context, callbackActivity);
222 callbackIntent.setAction(callbackAction);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800223 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
224 return serviceIntent;
225 }
226
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700227 private void createRawContact(Intent intent) {
228 String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
229 String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
230 List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES);
231 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
232
233 ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
234 operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
235 .withValue(RawContacts.ACCOUNT_NAME, accountName)
236 .withValue(RawContacts.ACCOUNT_TYPE, accountType)
237 .build());
238
239 int size = valueList.size();
240 for (int i = 0; i < size; i++) {
241 ContentValues values = valueList.get(i);
242 values.keySet().retainAll(ALLOWED_DATA_COLUMNS);
243 operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
244 .withValueBackReference(Data.RAW_CONTACT_ID, 0)
245 .withValues(values)
246 .build());
247 }
248
249 ContentResolver resolver = getContentResolver();
250 ContentProviderResult[] results;
251 try {
252 results = resolver.applyBatch(ContactsContract.AUTHORITY, operations);
253 } catch (Exception e) {
254 throw new RuntimeException("Failed to store new contact", e);
255 }
256
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700257 Uri rawContactUri = results[0].uri;
258 callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri));
259
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800260 deliverCallback(callbackIntent);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700261 }
262
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700263 /**
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800264 * Creates an intent that can be sent to this service to create a new raw contact
265 * using data presented as a set of ContentValues.
266 */
267 public static Intent createSaveContactIntent(Context context, EntityDeltaList state,
268 String saveModeExtraKey, int saveMode, Class<?> callbackActivity,
269 String callbackAction) {
270 Intent serviceIntent = new Intent(
271 context, ContactSaveService.class);
272 serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT);
273 serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state);
274
275 // Callback intent will be invoked by the service once the contact is
276 // saved. The service will put the URI of the new contact as "data" on
277 // the callback intent.
278 Intent callbackIntent = new Intent(context, callbackActivity);
279 callbackIntent.putExtra(saveModeExtraKey, saveMode);
280 callbackIntent.setAction(callbackAction);
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800281 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
282 return serviceIntent;
283 }
284
285 private void saveContact(Intent intent) {
286 EntityDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE);
287 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
288
289 // Trim any empty fields, and RawContacts, before persisting
290 final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
291 EntityModifier.trimEmpty(state, accountTypes);
292
293 Uri lookupUri = null;
294
295 final ContentResolver resolver = getContentResolver();
296
297 // Attempt to persist changes
298 int tries = 0;
299 while (tries++ < PERSIST_TRIES) {
300 try {
301 // Build operations and try applying
302 final ArrayList<ContentProviderOperation> diff = state.buildDiff();
Katherine Kuana007e442011-07-07 09:25:34 -0700303 if (DEBUG) {
304 Log.v(TAG, "Content Provider Operations:");
305 for (ContentProviderOperation operation : diff) {
306 Log.v(TAG, operation.toString());
307 }
308 }
309
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800310 ContentProviderResult[] results = null;
311 if (!diff.isEmpty()) {
312 results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
313 }
314
315 final long rawContactId = getRawContactId(state, diff, results);
316 if (rawContactId == -1) {
317 throw new IllegalStateException("Could not determine RawContact ID after save");
318 }
319 final Uri rawContactUri = ContentUris.withAppendedId(
320 RawContacts.CONTENT_URI, rawContactId);
321 lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri);
322 Log.v(TAG, "Saved contact. New URI: " + lookupUri);
323 break;
324
325 } catch (RemoteException e) {
326 // Something went wrong, bail without success
327 Log.e(TAG, "Problem persisting user edits", e);
328 break;
329
330 } catch (OperationApplicationException e) {
331 // Version consistency failed, re-parent change and try again
332 Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString());
333 final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN(");
334 boolean first = true;
335 final int count = state.size();
336 for (int i = 0; i < count; i++) {
337 Long rawContactId = state.getRawContactId(i);
338 if (rawContactId != null && rawContactId != -1) {
339 if (!first) {
340 sb.append(',');
341 }
342 sb.append(rawContactId);
343 first = false;
344 }
345 }
346 sb.append(")");
347
348 if (first) {
349 throw new IllegalStateException("Version consistency failed for a new contact");
350 }
351
352 final EntityDeltaList newState = EntityDeltaList.fromQuery(resolver,
353 sb.toString(), null, null);
354 state = EntityDeltaList.mergeAfter(newState, state);
355 }
356 }
357
358 callbackIntent.setData(lookupUri);
359
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800360 deliverCallback(callbackIntent);
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800361 }
362
363 private long getRawContactId(EntityDeltaList state,
364 final ArrayList<ContentProviderOperation> diff,
365 final ContentProviderResult[] results) {
366 long rawContactId = state.findRawContactId();
367 if (rawContactId != -1) {
368 return rawContactId;
369 }
370
371 final int diffSize = diff.size();
372 for (int i = 0; i < diffSize; i++) {
373 ContentProviderOperation operation = diff.get(i);
374 if (operation.getType() == ContentProviderOperation.TYPE_INSERT
375 && operation.getUri().getEncodedPath().contains(
376 RawContacts.CONTENT_URI.getEncodedPath())) {
377 return ContentUris.parseId(results[i].uri);
378 }
379 }
380 return -1;
381 }
382
383 /**
Katherine Kuan717e3432011-07-13 17:03:24 -0700384 * Creates an intent that can be sent to this service to create a new group as
385 * well as add new members at the same time.
386 *
387 * @param context of the application
388 * @param account in which the group should be created
389 * @param label is the name of the group (cannot be null)
390 * @param rawContactsToAdd is an array of raw contact IDs for contacts that
391 * should be added to the group
392 * @param callbackActivity is the activity to send the callback intent to
393 * @param callbackAction is the intent action for the callback intent
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700394 */
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700395
Katherine Kuan717e3432011-07-13 17:03:24 -0700396 public static Intent createNewGroupIntent(Context context, Account account,
397 String label, long[] rawContactsToAdd, Class<?> callbackActivity,
398 String callbackAction) {
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800399 Intent serviceIntent = new Intent(context, ContactSaveService.class);
400 serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP);
401 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
402 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
403 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label);
Katherine Kuan717e3432011-07-13 17:03:24 -0700404 serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700405
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800406 // Callback intent will be invoked by the service once the new group is
Katherine Kuan717e3432011-07-13 17:03:24 -0700407 // created.
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800408 Intent callbackIntent = new Intent(context, callbackActivity);
409 callbackIntent.setAction(callbackAction);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700410 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800411
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700412 return serviceIntent;
413 }
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800414
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800415 private void createGroup(Intent intent) {
Katherine Kuan717e3432011-07-13 17:03:24 -0700416 final String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
417 final String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
418 final String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
419 final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800420
421 ContentValues values = new ContentValues();
422 values.put(Groups.ACCOUNT_TYPE, accountType);
423 values.put(Groups.ACCOUNT_NAME, accountName);
424 values.put(Groups.TITLE, label);
425
Katherine Kuan717e3432011-07-13 17:03:24 -0700426 final ContentResolver resolver = getContentResolver();
427
428 // Create the new group
429 final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values);
430
431 // If there's no URI, then the insertion failed. Abort early because group members can't be
432 // added if the group doesn't exist
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800433 if (groupUri == null) {
Katherine Kuan717e3432011-07-13 17:03:24 -0700434 Log.e(TAG, "Couldn't create group with label " + label);
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800435 return;
436 }
437
Katherine Kuan717e3432011-07-13 17:03:24 -0700438 // Add new group members
439 addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri));
440
441 // TODO: Move this into the contact editor where it belongs. This needs to be integrated
442 // with the way other intent extras that are passed to the {@link ContactEditorActivity}.
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800443 values.clear();
444 values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
445 values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri));
446
447 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
Katherine Kuanc6b8afe2011-06-22 19:03:50 -0700448 callbackIntent.setData(groupUri);
Katherine Kuan717e3432011-07-13 17:03:24 -0700449 // TODO: This can be taken out when the above TODO is addressed
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800450 callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values));
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800451 deliverCallback(callbackIntent);
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800452 }
453
454 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800455 * Creates an intent that can be sent to this service to rename a group.
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800456 */
Katherine Kuanc6b8afe2011-06-22 19:03:50 -0700457 public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel,
458 Class<?> callbackActivity, String callbackAction) {
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800459 Intent serviceIntent = new Intent(context, ContactSaveService.class);
460 serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP);
461 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
462 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
Katherine Kuanc6b8afe2011-06-22 19:03:50 -0700463
464 // Callback intent will be invoked by the service once the group is renamed.
465 Intent callbackIntent = new Intent(context, callbackActivity);
466 callbackIntent.setAction(callbackAction);
467 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
468
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800469 return serviceIntent;
470 }
471
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800472 private void renameGroup(Intent intent) {
473 long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
474 String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
475
476 if (groupId == -1) {
477 Log.e(TAG, "Invalid arguments for renameGroup request");
478 return;
479 }
480
481 ContentValues values = new ContentValues();
482 values.put(Groups.TITLE, label);
Katherine Kuanc6b8afe2011-06-22 19:03:50 -0700483 final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
484 getContentResolver().update(groupUri, values, null, null);
485
486 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
487 callbackIntent.setData(groupUri);
488 deliverCallback(callbackIntent);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800489 }
490
491 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800492 * Creates an intent that can be sent to this service to delete a group.
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800493 */
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800494 public static Intent createGroupDeletionIntent(Context context, long groupId) {
495 Intent serviceIntent = new Intent(context, ContactSaveService.class);
496 serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800497 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800498 return serviceIntent;
499 }
500
501 private void deleteGroup(Intent intent) {
502 long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
503 if (groupId == -1) {
504 Log.e(TAG, "Invalid arguments for deleteGroup request");
505 return;
506 }
507
508 getContentResolver().delete(
509 ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null);
510 }
511
512 /**
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700513 * Creates an intent that can be sent to this service to rename a group as
514 * well as add and remove members from the group.
515 *
516 * @param context of the application
517 * @param groupId of the group that should be modified
518 * @param newLabel is the updated name of the group (can be null if the name
519 * should not be updated)
520 * @param rawContactsToAdd is an array of raw contact IDs for contacts that
521 * should be added to the group
522 * @param rawContactsToRemove is an array of raw contact IDs for contacts
523 * that should be removed from the group
524 * @param callbackActivity is the activity to send the callback intent to
525 * @param callbackAction is the intent action for the callback intent
526 */
527 public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel,
528 long[] rawContactsToAdd, long[] rawContactsToRemove,
529 Class<?> callbackActivity, String callbackAction) {
530 Intent serviceIntent = new Intent(context, ContactSaveService.class);
531 serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP);
532 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
533 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
534 serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
535 serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_REMOVE,
536 rawContactsToRemove);
537
538 // Callback intent will be invoked by the service once the group is updated
539 Intent callbackIntent = new Intent(context, callbackActivity);
540 callbackIntent.setAction(callbackAction);
541 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
542
543 return serviceIntent;
544 }
545
546 private void updateGroup(Intent intent) {
547 long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
548 String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
549 long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
550 long[] rawContactsToRemove = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_REMOVE);
551
552 if (groupId == -1) {
553 Log.e(TAG, "Invalid arguments for updateGroup request");
554 return;
555 }
556
557 final ContentResolver resolver = getContentResolver();
558 final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
559
560 // Update group name if necessary
561 if (label != null) {
562 ContentValues values = new ContentValues();
563 values.put(Groups.TITLE, label);
Katherine Kuan717e3432011-07-13 17:03:24 -0700564 resolver.update(groupUri, values, null, null);
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700565 }
566
Katherine Kuan717e3432011-07-13 17:03:24 -0700567 // Add and remove members if necessary
568 addMembersToGroup(resolver, rawContactsToAdd, groupId);
569 removeMembersFromGroup(resolver, rawContactsToRemove, groupId);
570
571 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
572 callbackIntent.setData(groupUri);
573 deliverCallback(callbackIntent);
574 }
575
576 private void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd,
577 long groupId) {
578 if (rawContactsToAdd == null) {
579 return;
580 }
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700581 for (long rawContactId : rawContactsToAdd) {
582 try {
583 final ArrayList<ContentProviderOperation> rawContactOperations =
584 new ArrayList<ContentProviderOperation>();
585
586 // Build an assert operation to ensure the contact is not already in the group
587 final ContentProviderOperation.Builder assertBuilder = ContentProviderOperation
588 .newAssertQuery(Data.CONTENT_URI);
589 assertBuilder.withSelection(Data.RAW_CONTACT_ID + "=? AND " +
590 Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
591 new String[] { String.valueOf(rawContactId),
592 GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
593 assertBuilder.withExpectedCount(0);
594 rawContactOperations.add(assertBuilder.build());
595
596 // Build an insert operation to add the contact to the group
597 final ContentProviderOperation.Builder insertBuilder = ContentProviderOperation
598 .newInsert(Data.CONTENT_URI);
599 insertBuilder.withValue(Data.RAW_CONTACT_ID, rawContactId);
600 insertBuilder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
601 insertBuilder.withValue(GroupMembership.GROUP_ROW_ID, groupId);
602 rawContactOperations.add(insertBuilder.build());
603
604 if (DEBUG) {
605 for (ContentProviderOperation operation : rawContactOperations) {
606 Log.v(TAG, operation.toString());
607 }
608 }
609
610 // Apply batch
611 ContentProviderResult[] results = null;
612 if (!rawContactOperations.isEmpty()) {
613 results = resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations);
614 }
615 } catch (RemoteException e) {
616 // Something went wrong, bail without success
617 Log.e(TAG, "Problem persisting user edits for raw contact ID " +
618 String.valueOf(rawContactId), e);
619 } catch (OperationApplicationException e) {
620 // The assert could have failed because the contact is already in the group,
621 // just continue to the next contact
622 Log.w(TAG, "Assert failed in adding raw contact ID " +
623 String.valueOf(rawContactId) + ". Already exists in group " +
624 String.valueOf(groupId), e);
625 }
626 }
Katherine Kuan717e3432011-07-13 17:03:24 -0700627 }
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700628
Katherine Kuan717e3432011-07-13 17:03:24 -0700629 private void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove,
630 long groupId) {
631 if (rawContactsToRemove == null) {
632 return;
633 }
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700634 for (long rawContactId : rawContactsToRemove) {
635 // Apply the delete operation on the data row for the given raw contact's
636 // membership in the given group. If no contact matches the provided selection, then
637 // nothing will be done. Just continue to the next contact.
638 getContentResolver().delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " +
639 Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
640 new String[] { String.valueOf(rawContactId),
641 GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
642 }
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700643 }
644
645 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800646 * Creates an intent that can be sent to this service to star or un-star a contact.
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800647 */
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800648 public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) {
649 Intent serviceIntent = new Intent(context, ContactSaveService.class);
650 serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED);
651 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
652 serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value);
653
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800654 return serviceIntent;
655 }
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800656
657 private void setStarred(Intent intent) {
658 Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
659 boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false);
660 if (contactUri == null) {
661 Log.e(TAG, "Invalid arguments for setStarred request");
662 return;
663 }
664
665 final ContentValues values = new ContentValues(1);
666 values.put(Contacts.STARRED, value);
667 getContentResolver().update(contactUri, values, null, null);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800668 }
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -0800669
670 /**
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700671 * Creates an intent that can be sent to this service to set the redirect to voicemail.
672 */
673 public static Intent createSetSendToVoicemail(Context context, Uri contactUri,
674 boolean value) {
675 Intent serviceIntent = new Intent(context, ContactSaveService.class);
676 serviceIntent.setAction(ContactSaveService.ACTION_SET_SEND_TO_VOICEMAIL);
677 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
678 serviceIntent.putExtra(ContactSaveService.EXTRA_SEND_TO_VOICEMAIL_FLAG, value);
679
680 return serviceIntent;
681 }
682
683 private void setSendToVoicemail(Intent intent) {
684 Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
685 boolean value = intent.getBooleanExtra(EXTRA_SEND_TO_VOICEMAIL_FLAG, false);
686 if (contactUri == null) {
687 Log.e(TAG, "Invalid arguments for setRedirectToVoicemail");
688 return;
689 }
690
691 final ContentValues values = new ContentValues(1);
692 values.put(Contacts.SEND_TO_VOICEMAIL, value);
693 getContentResolver().update(contactUri, values, null, null);
694 }
695
696 /**
697 * Creates an intent that can be sent to this service to save the contact's ringtone.
698 */
699 public static Intent createSetRingtone(Context context, Uri contactUri,
700 String value) {
701 Intent serviceIntent = new Intent(context, ContactSaveService.class);
702 serviceIntent.setAction(ContactSaveService.ACTION_SET_RINGTONE);
703 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
704 serviceIntent.putExtra(ContactSaveService.EXTRA_CUSTOM_RINGTONE, value);
705
706 return serviceIntent;
707 }
708
709 private void setRingtone(Intent intent) {
710 Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
711 String value = intent.getStringExtra(EXTRA_CUSTOM_RINGTONE);
712 if (contactUri == null) {
713 Log.e(TAG, "Invalid arguments for setRingtone");
714 return;
715 }
716 ContentValues values = new ContentValues(1);
717 values.put(Contacts.CUSTOM_RINGTONE, value);
718 getContentResolver().update(contactUri, values, null, null);
719 }
720
721 /**
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -0800722 * Creates an intent that sets the selected data item as super primary (default)
723 */
724 public static Intent createSetSuperPrimaryIntent(Context context, long dataId) {
725 Intent serviceIntent = new Intent(context, ContactSaveService.class);
726 serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY);
727 serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
728 return serviceIntent;
729 }
730
731 private void setSuperPrimary(Intent intent) {
732 long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
733 if (dataId == -1) {
734 Log.e(TAG, "Invalid arguments for setSuperPrimary request");
735 return;
736 }
737
738 // Update the primary values in the data record.
739 ContentValues values = new ContentValues(1);
740 values.put(Data.IS_SUPER_PRIMARY, 1);
741 values.put(Data.IS_PRIMARY, 1);
742
743 getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
744 values, null, null);
745 }
746
747 /**
748 * Creates an intent that clears the primary flag of all data items that belong to the same
749 * raw_contact as the given data item. Will only clear, if the data item was primary before
750 * this call
751 */
752 public static Intent createClearPrimaryIntent(Context context, long dataId) {
753 Intent serviceIntent = new Intent(context, ContactSaveService.class);
754 serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY);
755 serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
756 return serviceIntent;
757 }
758
759 private void clearPrimary(Intent intent) {
760 long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
761 if (dataId == -1) {
762 Log.e(TAG, "Invalid arguments for clearPrimary request");
763 return;
764 }
765
766 // Update the primary values in the data record.
767 ContentValues values = new ContentValues(1);
768 values.put(Data.IS_SUPER_PRIMARY, 0);
769 values.put(Data.IS_PRIMARY, 0);
770
771 getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
772 values, null, null);
773 }
Dmitri Plotnikov7d8cabb2010-11-24 17:40:01 -0800774
775 /**
776 * Creates an intent that can be sent to this service to delete a contact.
777 */
778 public static Intent createDeleteContactIntent(Context context, Uri contactUri) {
779 Intent serviceIntent = new Intent(context, ContactSaveService.class);
780 serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT);
781 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
782 return serviceIntent;
783 }
784
785 private void deleteContact(Intent intent) {
786 Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
787 if (contactUri == null) {
788 Log.e(TAG, "Invalid arguments for deleteContact request");
789 return;
790 }
791
792 getContentResolver().delete(contactUri, null, null);
793 }
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800794
795 /**
796 * Creates an intent that can be sent to this service to join two contacts.
797 */
798 public static Intent createJoinContactsIntent(Context context, long contactId1,
799 long contactId2, boolean contactWritable,
800 Class<?> callbackActivity, String callbackAction) {
801 Intent serviceIntent = new Intent(context, ContactSaveService.class);
802 serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS);
803 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1);
804 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2);
805 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_WRITABLE, contactWritable);
806
807 // Callback intent will be invoked by the service once the contacts are joined.
808 Intent callbackIntent = new Intent(context, callbackActivity);
809 callbackIntent.setAction(callbackAction);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800810 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
811
812 return serviceIntent;
813 }
814
815
816 private interface JoinContactQuery {
817 String[] PROJECTION = {
818 RawContacts._ID,
819 RawContacts.CONTACT_ID,
820 RawContacts.NAME_VERIFIED,
821 RawContacts.DISPLAY_NAME_SOURCE,
822 };
823
824 String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?";
825
826 int _ID = 0;
827 int CONTACT_ID = 1;
828 int NAME_VERIFIED = 2;
829 int DISPLAY_NAME_SOURCE = 3;
830 }
831
832 private void joinContacts(Intent intent) {
833 long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1);
834 long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1);
835 boolean writable = intent.getBooleanExtra(EXTRA_CONTACT_WRITABLE, false);
836 if (contactId1 == -1 || contactId2 == -1) {
837 Log.e(TAG, "Invalid arguments for joinContacts request");
838 return;
839 }
840
841 final ContentResolver resolver = getContentResolver();
842
843 // Load raw contact IDs for all raw contacts involved - currently edited and selected
844 // in the join UIs
845 Cursor c = resolver.query(RawContacts.CONTENT_URI,
846 JoinContactQuery.PROJECTION,
847 JoinContactQuery.SELECTION,
848 new String[]{String.valueOf(contactId1), String.valueOf(contactId2)}, null);
849
850 long rawContactIds[];
851 long verifiedNameRawContactId = -1;
852 try {
853 int maxDisplayNameSource = -1;
854 rawContactIds = new long[c.getCount()];
855 for (int i = 0; i < rawContactIds.length; i++) {
856 c.moveToPosition(i);
857 long rawContactId = c.getLong(JoinContactQuery._ID);
858 rawContactIds[i] = rawContactId;
859 int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
860 if (nameSource > maxDisplayNameSource) {
861 maxDisplayNameSource = nameSource;
862 }
863 }
864
865 // Find an appropriate display name for the joined contact:
866 // if should have a higher DisplayNameSource or be the name
867 // of the original contact that we are joining with another.
868 if (writable) {
869 for (int i = 0; i < rawContactIds.length; i++) {
870 c.moveToPosition(i);
871 if (c.getLong(JoinContactQuery.CONTACT_ID) == contactId1) {
872 int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
873 if (nameSource == maxDisplayNameSource
874 && (verifiedNameRawContactId == -1
875 || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0)) {
876 verifiedNameRawContactId = c.getLong(JoinContactQuery._ID);
877 }
878 }
879 }
880 }
881 } finally {
882 c.close();
883 }
884
885 // For each pair of raw contacts, insert an aggregation exception
886 ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
887 for (int i = 0; i < rawContactIds.length; i++) {
888 for (int j = 0; j < rawContactIds.length; j++) {
889 if (i != j) {
890 buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
891 }
892 }
893 }
894
895 // Mark the original contact as "name verified" to make sure that the contact
896 // display name does not change as a result of the join
897 if (verifiedNameRawContactId != -1) {
898 Builder builder = ContentProviderOperation.newUpdate(
899 ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId));
900 builder.withValue(RawContacts.NAME_VERIFIED, 1);
901 operations.add(builder.build());
902 }
903
904 boolean success = false;
905 // Apply all aggregation exceptions as one batch
906 try {
907 resolver.applyBatch(ContactsContract.AUTHORITY, operations);
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800908 showToast(R.string.contactsJoinedMessage);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800909 success = true;
910 } catch (RemoteException e) {
911 Log.e(TAG, "Failed to apply aggregation exception batch", e);
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800912 showToast(R.string.contactSavedErrorToast);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800913 } catch (OperationApplicationException e) {
914 Log.e(TAG, "Failed to apply aggregation exception batch", e);
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800915 showToast(R.string.contactSavedErrorToast);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800916 }
917
918 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
919 if (success) {
920 Uri uri = RawContacts.getContactLookupUri(resolver,
921 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
922 callbackIntent.setData(uri);
923 }
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800924 deliverCallback(callbackIntent);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800925 }
926
927 /**
928 * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation.
929 */
930 private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations,
931 long rawContactId1, long rawContactId2) {
932 Builder builder =
933 ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
934 builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
935 builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
936 builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
937 operations.add(builder.build());
938 }
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800939
940 /**
941 * Shows a toast on the UI thread.
942 */
943 private void showToast(final int message) {
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800944 mMainHandler.post(new Runnable() {
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800945
946 @Override
947 public void run() {
948 Toast.makeText(ContactSaveService.this, message, Toast.LENGTH_LONG).show();
949 }
950 });
951 }
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800952
953 private void deliverCallback(final Intent callbackIntent) {
954 mMainHandler.post(new Runnable() {
955
956 @Override
957 public void run() {
958 deliverCallbackOnUiThread(callbackIntent);
959 }
960 });
961 }
962
963 void deliverCallbackOnUiThread(final Intent callbackIntent) {
964 // TODO: this assumes that if there are multiple instances of the same
965 // activity registered, the last one registered is the one waiting for
966 // the callback. Validity of this assumption needs to be verified.
967 synchronized (sListeners) {
968 for (Listener listener : sListeners) {
969 if (callbackIntent.getComponent().equals(
970 ((Activity) listener).getIntent().getComponent())) {
971 listener.onServiceCompleted(callbackIntent);
972 return;
973 }
974 }
975 }
976 }
Daniel Lehmann173ffe12010-06-14 18:19:10 -0700977}