blob: 221796a6008da202136cdeb138d7e24607ab8a64 [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;
Dave Santoro2b3f3c52011-07-26 17:35:42 -070020import com.android.contacts.model.AccountWithDataSet;
Dmitri Plotnikova0114142011-02-15 13:53:21 -080021import com.android.contacts.model.EntityDeltaList;
22import com.android.contacts.model.EntityModifier;
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -080023import com.google.android.collect.Lists;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070024import com.google.android.collect.Sets;
25
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";
Dave Santoro2b3f3c52011-07-26 17:35:42 -070071 public static final String EXTRA_DATA_SET = "dataSet";
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070072 public static final String EXTRA_CONTENT_VALUES = "contentValues";
73 public static final String EXTRA_CALLBACK_INTENT = "callbackIntent";
74
Dmitri Plotnikova0114142011-02-15 13:53:21 -080075 public static final String ACTION_SAVE_CONTACT = "saveContact";
76 public static final String EXTRA_CONTACT_STATE = "state";
77 public static final String EXTRA_SAVE_MODE = "saveMode";
Daniel Lehmann173ffe12010-06-14 18:19:10 -070078
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -080079 public static final String ACTION_CREATE_GROUP = "createGroup";
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -080080 public static final String ACTION_RENAME_GROUP = "renameGroup";
81 public static final String ACTION_DELETE_GROUP = "deleteGroup";
Katherine Kuan2d851cc2011-07-05 16:23:27 -070082 public static final String ACTION_UPDATE_GROUP = "updateGroup";
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -080083 public static final String EXTRA_GROUP_ID = "groupId";
84 public static final String EXTRA_GROUP_LABEL = "groupLabel";
Katherine Kuan2d851cc2011-07-05 16:23:27 -070085 public static final String EXTRA_RAW_CONTACTS_TO_ADD = "rawContactsToAdd";
86 public static final String EXTRA_RAW_CONTACTS_TO_REMOVE = "rawContactsToRemove";
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -080087
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -080088 public static final String ACTION_SET_STARRED = "setStarred";
Dmitri Plotnikov7d8cabb2010-11-24 17:40:01 -080089 public static final String ACTION_DELETE_CONTACT = "delete";
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -080090 public static final String EXTRA_CONTACT_URI = "contactUri";
91 public static final String EXTRA_STARRED_FLAG = "starred";
92
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -080093 public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary";
94 public static final String ACTION_CLEAR_PRIMARY = "clearPrimary";
95 public static final String EXTRA_DATA_ID = "dataId";
96
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080097 public static final String ACTION_JOIN_CONTACTS = "joinContacts";
98 public static final String EXTRA_CONTACT_ID1 = "contactId1";
99 public static final String EXTRA_CONTACT_ID2 = "contactId2";
100 public static final String EXTRA_CONTACT_WRITABLE = "contactWritable";
101
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700102 public static final String ACTION_SET_SEND_TO_VOICEMAIL = "sendToVoicemail";
103 public static final String EXTRA_SEND_TO_VOICEMAIL_FLAG = "sendToVoicemailFlag";
104
105 public static final String ACTION_SET_RINGTONE = "setRingtone";
106 public static final String EXTRA_CUSTOM_RINGTONE = "customRingtone";
107
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700108 private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet(
109 Data.MIMETYPE,
110 Data.IS_PRIMARY,
111 Data.DATA1,
112 Data.DATA2,
113 Data.DATA3,
114 Data.DATA4,
115 Data.DATA5,
116 Data.DATA6,
117 Data.DATA7,
118 Data.DATA8,
119 Data.DATA9,
120 Data.DATA10,
121 Data.DATA11,
122 Data.DATA12,
123 Data.DATA13,
124 Data.DATA14,
125 Data.DATA15
126 );
127
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800128 private static final int PERSIST_TRIES = 3;
129
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800130 public interface Listener {
131 public void onServiceCompleted(Intent callbackIntent);
132 }
133
134 private static final LinkedList<Listener> sListeners = new LinkedList<Listener>();
135
136 private Handler mMainHandler;
137
Daniel Lehmann173ffe12010-06-14 18:19:10 -0700138 public ContactSaveService() {
139 super(TAG);
140 setIntentRedelivery(true);
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800141 mMainHandler = new Handler(Looper.getMainLooper());
142 }
143
144 public static void registerListener(Listener listener) {
145 if (!(listener instanceof Activity)) {
146 throw new ClassCastException("Only activities can be registered to"
147 + " receive callback from " + ContactSaveService.class.getName());
148 }
149 synchronized (sListeners) {
150 sListeners.addFirst(listener);
151 }
152 }
153
154 public static void unregisterListener(Listener listener) {
155 synchronized (sListeners) {
156 sListeners.remove(listener);
157 }
Daniel Lehmann173ffe12010-06-14 18:19:10 -0700158 }
159
160 @Override
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800161 public Object getSystemService(String name) {
162 Object service = super.getSystemService(name);
163 if (service != null) {
164 return service;
165 }
166
167 return getApplicationContext().getSystemService(name);
168 }
169
170 @Override
Daniel Lehmann173ffe12010-06-14 18:19:10 -0700171 protected void onHandleIntent(Intent intent) {
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700172 String action = intent.getAction();
173 if (ACTION_NEW_RAW_CONTACT.equals(action)) {
174 createRawContact(intent);
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800175 } else if (ACTION_SAVE_CONTACT.equals(action)) {
176 saveContact(intent);
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800177 } else if (ACTION_CREATE_GROUP.equals(action)) {
178 createGroup(intent);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800179 } else if (ACTION_RENAME_GROUP.equals(action)) {
180 renameGroup(intent);
181 } else if (ACTION_DELETE_GROUP.equals(action)) {
182 deleteGroup(intent);
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700183 } else if (ACTION_UPDATE_GROUP.equals(action)) {
184 updateGroup(intent);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800185 } else if (ACTION_SET_STARRED.equals(action)) {
186 setStarred(intent);
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -0800187 } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) {
188 setSuperPrimary(intent);
189 } else if (ACTION_CLEAR_PRIMARY.equals(action)) {
190 clearPrimary(intent);
Dmitri Plotnikov7d8cabb2010-11-24 17:40:01 -0800191 } else if (ACTION_DELETE_CONTACT.equals(action)) {
192 deleteContact(intent);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800193 } else if (ACTION_JOIN_CONTACTS.equals(action)) {
194 joinContacts(intent);
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700195 } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) {
196 setSendToVoicemail(intent);
197 } else if (ACTION_SET_RINGTONE.equals(action)) {
198 setRingtone(intent);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700199 }
200 }
201
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800202 /**
203 * Creates an intent that can be sent to this service to create a new raw contact
204 * using data presented as a set of ContentValues.
205 */
206 public static Intent createNewRawContactIntent(Context context,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700207 ArrayList<ContentValues> values, AccountWithDataSet account,
208 Class<?> callbackActivity, String callbackAction) {
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800209 Intent serviceIntent = new Intent(
210 context, ContactSaveService.class);
211 serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT);
212 if (account != null) {
213 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
214 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700215 serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800216 }
217 serviceIntent.putParcelableArrayListExtra(
218 ContactSaveService.EXTRA_CONTENT_VALUES, values);
219
220 // Callback intent will be invoked by the service once the new contact is
221 // created. The service will put the URI of the new contact as "data" on
222 // the callback intent.
223 Intent callbackIntent = new Intent(context, callbackActivity);
224 callbackIntent.setAction(callbackAction);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800225 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
226 return serviceIntent;
227 }
228
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700229 private void createRawContact(Intent intent) {
230 String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
231 String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700232 String dataSet = intent.getStringExtra(EXTRA_DATA_SET);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700233 List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES);
234 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
235
236 ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
237 operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
238 .withValue(RawContacts.ACCOUNT_NAME, accountName)
239 .withValue(RawContacts.ACCOUNT_TYPE, accountType)
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700240 .withValue(RawContacts.DATA_SET, dataSet)
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700241 .build());
242
243 int size = valueList.size();
244 for (int i = 0; i < size; i++) {
245 ContentValues values = valueList.get(i);
246 values.keySet().retainAll(ALLOWED_DATA_COLUMNS);
247 operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
248 .withValueBackReference(Data.RAW_CONTACT_ID, 0)
249 .withValues(values)
250 .build());
251 }
252
253 ContentResolver resolver = getContentResolver();
254 ContentProviderResult[] results;
255 try {
256 results = resolver.applyBatch(ContactsContract.AUTHORITY, operations);
257 } catch (Exception e) {
258 throw new RuntimeException("Failed to store new contact", e);
259 }
260
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700261 Uri rawContactUri = results[0].uri;
262 callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri));
263
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800264 deliverCallback(callbackIntent);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700265 }
266
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700267 /**
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800268 * Creates an intent that can be sent to this service to create a new raw contact
269 * using data presented as a set of ContentValues.
270 */
271 public static Intent createSaveContactIntent(Context context, EntityDeltaList state,
272 String saveModeExtraKey, int saveMode, Class<?> callbackActivity,
273 String callbackAction) {
274 Intent serviceIntent = new Intent(
275 context, ContactSaveService.class);
276 serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT);
277 serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state);
278
279 // Callback intent will be invoked by the service once the contact is
280 // saved. The service will put the URI of the new contact as "data" on
281 // the callback intent.
282 Intent callbackIntent = new Intent(context, callbackActivity);
283 callbackIntent.putExtra(saveModeExtraKey, saveMode);
284 callbackIntent.setAction(callbackAction);
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800285 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
286 return serviceIntent;
287 }
288
289 private void saveContact(Intent intent) {
290 EntityDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE);
291 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
292
293 // Trim any empty fields, and RawContacts, before persisting
294 final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
295 EntityModifier.trimEmpty(state, accountTypes);
296
297 Uri lookupUri = null;
298
299 final ContentResolver resolver = getContentResolver();
300
301 // Attempt to persist changes
302 int tries = 0;
303 while (tries++ < PERSIST_TRIES) {
304 try {
305 // Build operations and try applying
306 final ArrayList<ContentProviderOperation> diff = state.buildDiff();
Katherine Kuana007e442011-07-07 09:25:34 -0700307 if (DEBUG) {
308 Log.v(TAG, "Content Provider Operations:");
309 for (ContentProviderOperation operation : diff) {
310 Log.v(TAG, operation.toString());
311 }
312 }
313
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800314 ContentProviderResult[] results = null;
315 if (!diff.isEmpty()) {
316 results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
317 }
318
319 final long rawContactId = getRawContactId(state, diff, results);
320 if (rawContactId == -1) {
321 throw new IllegalStateException("Could not determine RawContact ID after save");
322 }
323 final Uri rawContactUri = ContentUris.withAppendedId(
324 RawContacts.CONTENT_URI, rawContactId);
325 lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri);
326 Log.v(TAG, "Saved contact. New URI: " + lookupUri);
327 break;
328
329 } catch (RemoteException e) {
330 // Something went wrong, bail without success
331 Log.e(TAG, "Problem persisting user edits", e);
332 break;
333
334 } catch (OperationApplicationException e) {
335 // Version consistency failed, re-parent change and try again
336 Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString());
337 final StringBuilder sb = new StringBuilder(RawContacts._ID + " IN(");
338 boolean first = true;
339 final int count = state.size();
340 for (int i = 0; i < count; i++) {
341 Long rawContactId = state.getRawContactId(i);
342 if (rawContactId != null && rawContactId != -1) {
343 if (!first) {
344 sb.append(',');
345 }
346 sb.append(rawContactId);
347 first = false;
348 }
349 }
350 sb.append(")");
351
352 if (first) {
353 throw new IllegalStateException("Version consistency failed for a new contact");
354 }
355
356 final EntityDeltaList newState = EntityDeltaList.fromQuery(resolver,
357 sb.toString(), null, null);
358 state = EntityDeltaList.mergeAfter(newState, state);
359 }
360 }
361
362 callbackIntent.setData(lookupUri);
363
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800364 deliverCallback(callbackIntent);
Dmitri Plotnikova0114142011-02-15 13:53:21 -0800365 }
366
367 private long getRawContactId(EntityDeltaList state,
368 final ArrayList<ContentProviderOperation> diff,
369 final ContentProviderResult[] results) {
370 long rawContactId = state.findRawContactId();
371 if (rawContactId != -1) {
372 return rawContactId;
373 }
374
375 final int diffSize = diff.size();
376 for (int i = 0; i < diffSize; i++) {
377 ContentProviderOperation operation = diff.get(i);
378 if (operation.getType() == ContentProviderOperation.TYPE_INSERT
379 && operation.getUri().getEncodedPath().contains(
380 RawContacts.CONTENT_URI.getEncodedPath())) {
381 return ContentUris.parseId(results[i].uri);
382 }
383 }
384 return -1;
385 }
386
387 /**
Katherine Kuan717e3432011-07-13 17:03:24 -0700388 * Creates an intent that can be sent to this service to create a new group as
389 * well as add new members at the same time.
390 *
391 * @param context of the application
392 * @param account in which the group should be created
393 * @param label is the name of the group (cannot be null)
394 * @param rawContactsToAdd is an array of raw contact IDs for contacts that
395 * should be added to the group
396 * @param callbackActivity is the activity to send the callback intent to
397 * @param callbackAction is the intent action for the callback intent
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700398 */
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700399 public static Intent createNewGroupIntent(Context context, AccountWithDataSet account,
Katherine Kuan717e3432011-07-13 17:03:24 -0700400 String label, long[] rawContactsToAdd, Class<?> callbackActivity,
401 String callbackAction) {
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800402 Intent serviceIntent = new Intent(context, ContactSaveService.class);
403 serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP);
404 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
405 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700406 serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_SET, account.dataSet);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800407 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label);
Katherine Kuan717e3432011-07-13 17:03:24 -0700408 serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700409
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800410 // Callback intent will be invoked by the service once the new group is
Katherine Kuan717e3432011-07-13 17:03:24 -0700411 // created.
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800412 Intent callbackIntent = new Intent(context, callbackActivity);
413 callbackIntent.setAction(callbackAction);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700414 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800415
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700416 return serviceIntent;
417 }
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800418
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800419 private void createGroup(Intent intent) {
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700420 String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
421 String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
422 String dataSet = intent.getStringExtra(EXTRA_DATA_SET);
423 String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
Katherine Kuan717e3432011-07-13 17:03:24 -0700424 final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800425
426 ContentValues values = new ContentValues();
427 values.put(Groups.ACCOUNT_TYPE, accountType);
428 values.put(Groups.ACCOUNT_NAME, accountName);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700429 values.put(Groups.DATA_SET, dataSet);
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800430 values.put(Groups.TITLE, label);
431
Katherine Kuan717e3432011-07-13 17:03:24 -0700432 final ContentResolver resolver = getContentResolver();
433
434 // Create the new group
435 final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values);
436
437 // If there's no URI, then the insertion failed. Abort early because group members can't be
438 // added if the group doesn't exist
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800439 if (groupUri == null) {
Katherine Kuan717e3432011-07-13 17:03:24 -0700440 Log.e(TAG, "Couldn't create group with label " + label);
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800441 return;
442 }
443
Katherine Kuan717e3432011-07-13 17:03:24 -0700444 // Add new group members
445 addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri));
446
447 // TODO: Move this into the contact editor where it belongs. This needs to be integrated
448 // with the way other intent extras that are passed to the {@link ContactEditorActivity}.
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800449 values.clear();
450 values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
451 values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri));
452
453 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
Katherine Kuanc6b8afe2011-06-22 19:03:50 -0700454 callbackIntent.setData(groupUri);
Katherine Kuan717e3432011-07-13 17:03:24 -0700455 // TODO: This can be taken out when the above TODO is addressed
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800456 callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values));
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800457 deliverCallback(callbackIntent);
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800458 }
459
460 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800461 * Creates an intent that can be sent to this service to rename a group.
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800462 */
Katherine Kuanc6b8afe2011-06-22 19:03:50 -0700463 public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel,
464 Class<?> callbackActivity, String callbackAction) {
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800465 Intent serviceIntent = new Intent(context, ContactSaveService.class);
466 serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP);
467 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
468 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
Katherine Kuanc6b8afe2011-06-22 19:03:50 -0700469
470 // Callback intent will be invoked by the service once the group is renamed.
471 Intent callbackIntent = new Intent(context, callbackActivity);
472 callbackIntent.setAction(callbackAction);
473 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
474
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800475 return serviceIntent;
476 }
477
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800478 private void renameGroup(Intent intent) {
479 long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
480 String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
481
482 if (groupId == -1) {
483 Log.e(TAG, "Invalid arguments for renameGroup request");
484 return;
485 }
486
487 ContentValues values = new ContentValues();
488 values.put(Groups.TITLE, label);
Katherine Kuanc6b8afe2011-06-22 19:03:50 -0700489 final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
490 getContentResolver().update(groupUri, values, null, null);
491
492 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
493 callbackIntent.setData(groupUri);
494 deliverCallback(callbackIntent);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800495 }
496
497 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800498 * Creates an intent that can be sent to this service to delete a group.
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800499 */
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800500 public static Intent createGroupDeletionIntent(Context context, long groupId) {
501 Intent serviceIntent = new Intent(context, ContactSaveService.class);
502 serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800503 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800504 return serviceIntent;
505 }
506
507 private void deleteGroup(Intent intent) {
508 long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
509 if (groupId == -1) {
510 Log.e(TAG, "Invalid arguments for deleteGroup request");
511 return;
512 }
513
514 getContentResolver().delete(
515 ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null);
516 }
517
518 /**
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700519 * Creates an intent that can be sent to this service to rename a group as
520 * well as add and remove members from the group.
521 *
522 * @param context of the application
523 * @param groupId of the group that should be modified
524 * @param newLabel is the updated name of the group (can be null if the name
525 * should not be updated)
526 * @param rawContactsToAdd is an array of raw contact IDs for contacts that
527 * should be added to the group
528 * @param rawContactsToRemove is an array of raw contact IDs for contacts
529 * that should be removed from the group
530 * @param callbackActivity is the activity to send the callback intent to
531 * @param callbackAction is the intent action for the callback intent
532 */
533 public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel,
534 long[] rawContactsToAdd, long[] rawContactsToRemove,
535 Class<?> callbackActivity, String callbackAction) {
536 Intent serviceIntent = new Intent(context, ContactSaveService.class);
537 serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP);
538 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
539 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
540 serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
541 serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_REMOVE,
542 rawContactsToRemove);
543
544 // Callback intent will be invoked by the service once the group is updated
545 Intent callbackIntent = new Intent(context, callbackActivity);
546 callbackIntent.setAction(callbackAction);
547 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
548
549 return serviceIntent;
550 }
551
552 private void updateGroup(Intent intent) {
553 long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
554 String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
555 long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
556 long[] rawContactsToRemove = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_REMOVE);
557
558 if (groupId == -1) {
559 Log.e(TAG, "Invalid arguments for updateGroup request");
560 return;
561 }
562
563 final ContentResolver resolver = getContentResolver();
564 final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId);
565
566 // Update group name if necessary
567 if (label != null) {
568 ContentValues values = new ContentValues();
569 values.put(Groups.TITLE, label);
Katherine Kuan717e3432011-07-13 17:03:24 -0700570 resolver.update(groupUri, values, null, null);
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700571 }
572
Katherine Kuan717e3432011-07-13 17:03:24 -0700573 // Add and remove members if necessary
574 addMembersToGroup(resolver, rawContactsToAdd, groupId);
575 removeMembersFromGroup(resolver, rawContactsToRemove, groupId);
576
577 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
578 callbackIntent.setData(groupUri);
579 deliverCallback(callbackIntent);
580 }
581
582 private void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd,
583 long groupId) {
584 if (rawContactsToAdd == null) {
585 return;
586 }
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700587 for (long rawContactId : rawContactsToAdd) {
588 try {
589 final ArrayList<ContentProviderOperation> rawContactOperations =
590 new ArrayList<ContentProviderOperation>();
591
592 // Build an assert operation to ensure the contact is not already in the group
593 final ContentProviderOperation.Builder assertBuilder = ContentProviderOperation
594 .newAssertQuery(Data.CONTENT_URI);
595 assertBuilder.withSelection(Data.RAW_CONTACT_ID + "=? AND " +
596 Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
597 new String[] { String.valueOf(rawContactId),
598 GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
599 assertBuilder.withExpectedCount(0);
600 rawContactOperations.add(assertBuilder.build());
601
602 // Build an insert operation to add the contact to the group
603 final ContentProviderOperation.Builder insertBuilder = ContentProviderOperation
604 .newInsert(Data.CONTENT_URI);
605 insertBuilder.withValue(Data.RAW_CONTACT_ID, rawContactId);
606 insertBuilder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
607 insertBuilder.withValue(GroupMembership.GROUP_ROW_ID, groupId);
608 rawContactOperations.add(insertBuilder.build());
609
610 if (DEBUG) {
611 for (ContentProviderOperation operation : rawContactOperations) {
612 Log.v(TAG, operation.toString());
613 }
614 }
615
616 // Apply batch
617 ContentProviderResult[] results = null;
618 if (!rawContactOperations.isEmpty()) {
619 results = resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations);
620 }
621 } catch (RemoteException e) {
622 // Something went wrong, bail without success
623 Log.e(TAG, "Problem persisting user edits for raw contact ID " +
624 String.valueOf(rawContactId), e);
625 } catch (OperationApplicationException e) {
626 // The assert could have failed because the contact is already in the group,
627 // just continue to the next contact
628 Log.w(TAG, "Assert failed in adding raw contact ID " +
629 String.valueOf(rawContactId) + ". Already exists in group " +
630 String.valueOf(groupId), e);
631 }
632 }
Katherine Kuan717e3432011-07-13 17:03:24 -0700633 }
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700634
Katherine Kuan717e3432011-07-13 17:03:24 -0700635 private void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove,
636 long groupId) {
637 if (rawContactsToRemove == null) {
638 return;
639 }
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700640 for (long rawContactId : rawContactsToRemove) {
641 // Apply the delete operation on the data row for the given raw contact's
642 // membership in the given group. If no contact matches the provided selection, then
643 // nothing will be done. Just continue to the next contact.
644 getContentResolver().delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " +
645 Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
646 new String[] { String.valueOf(rawContactId),
647 GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
648 }
Katherine Kuan2d851cc2011-07-05 16:23:27 -0700649 }
650
651 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800652 * Creates an intent that can be sent to this service to star or un-star a contact.
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800653 */
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800654 public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) {
655 Intent serviceIntent = new Intent(context, ContactSaveService.class);
656 serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED);
657 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
658 serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value);
659
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800660 return serviceIntent;
661 }
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800662
663 private void setStarred(Intent intent) {
664 Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
665 boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false);
666 if (contactUri == null) {
667 Log.e(TAG, "Invalid arguments for setStarred request");
668 return;
669 }
670
671 final ContentValues values = new ContentValues(1);
672 values.put(Contacts.STARRED, value);
673 getContentResolver().update(contactUri, values, null, null);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800674 }
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -0800675
676 /**
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700677 * Creates an intent that can be sent to this service to set the redirect to voicemail.
678 */
679 public static Intent createSetSendToVoicemail(Context context, Uri contactUri,
680 boolean value) {
681 Intent serviceIntent = new Intent(context, ContactSaveService.class);
682 serviceIntent.setAction(ContactSaveService.ACTION_SET_SEND_TO_VOICEMAIL);
683 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
684 serviceIntent.putExtra(ContactSaveService.EXTRA_SEND_TO_VOICEMAIL_FLAG, value);
685
686 return serviceIntent;
687 }
688
689 private void setSendToVoicemail(Intent intent) {
690 Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
691 boolean value = intent.getBooleanExtra(EXTRA_SEND_TO_VOICEMAIL_FLAG, false);
692 if (contactUri == null) {
693 Log.e(TAG, "Invalid arguments for setRedirectToVoicemail");
694 return;
695 }
696
697 final ContentValues values = new ContentValues(1);
698 values.put(Contacts.SEND_TO_VOICEMAIL, value);
699 getContentResolver().update(contactUri, values, null, null);
700 }
701
702 /**
703 * Creates an intent that can be sent to this service to save the contact's ringtone.
704 */
705 public static Intent createSetRingtone(Context context, Uri contactUri,
706 String value) {
707 Intent serviceIntent = new Intent(context, ContactSaveService.class);
708 serviceIntent.setAction(ContactSaveService.ACTION_SET_RINGTONE);
709 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
710 serviceIntent.putExtra(ContactSaveService.EXTRA_CUSTOM_RINGTONE, value);
711
712 return serviceIntent;
713 }
714
715 private void setRingtone(Intent intent) {
716 Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
717 String value = intent.getStringExtra(EXTRA_CUSTOM_RINGTONE);
718 if (contactUri == null) {
719 Log.e(TAG, "Invalid arguments for setRingtone");
720 return;
721 }
722 ContentValues values = new ContentValues(1);
723 values.put(Contacts.CUSTOM_RINGTONE, value);
724 getContentResolver().update(contactUri, values, null, null);
725 }
726
727 /**
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -0800728 * Creates an intent that sets the selected data item as super primary (default)
729 */
730 public static Intent createSetSuperPrimaryIntent(Context context, long dataId) {
731 Intent serviceIntent = new Intent(context, ContactSaveService.class);
732 serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY);
733 serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
734 return serviceIntent;
735 }
736
737 private void setSuperPrimary(Intent intent) {
738 long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
739 if (dataId == -1) {
740 Log.e(TAG, "Invalid arguments for setSuperPrimary request");
741 return;
742 }
743
744 // Update the primary values in the data record.
745 ContentValues values = new ContentValues(1);
746 values.put(Data.IS_SUPER_PRIMARY, 1);
747 values.put(Data.IS_PRIMARY, 1);
748
749 getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
750 values, null, null);
751 }
752
753 /**
754 * Creates an intent that clears the primary flag of all data items that belong to the same
755 * raw_contact as the given data item. Will only clear, if the data item was primary before
756 * this call
757 */
758 public static Intent createClearPrimaryIntent(Context context, long dataId) {
759 Intent serviceIntent = new Intent(context, ContactSaveService.class);
760 serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY);
761 serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
762 return serviceIntent;
763 }
764
765 private void clearPrimary(Intent intent) {
766 long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
767 if (dataId == -1) {
768 Log.e(TAG, "Invalid arguments for clearPrimary request");
769 return;
770 }
771
772 // Update the primary values in the data record.
773 ContentValues values = new ContentValues(1);
774 values.put(Data.IS_SUPER_PRIMARY, 0);
775 values.put(Data.IS_PRIMARY, 0);
776
777 getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
778 values, null, null);
779 }
Dmitri Plotnikov7d8cabb2010-11-24 17:40:01 -0800780
781 /**
782 * Creates an intent that can be sent to this service to delete a contact.
783 */
784 public static Intent createDeleteContactIntent(Context context, Uri contactUri) {
785 Intent serviceIntent = new Intent(context, ContactSaveService.class);
786 serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT);
787 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
788 return serviceIntent;
789 }
790
791 private void deleteContact(Intent intent) {
792 Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
793 if (contactUri == null) {
794 Log.e(TAG, "Invalid arguments for deleteContact request");
795 return;
796 }
797
798 getContentResolver().delete(contactUri, null, null);
799 }
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800800
801 /**
802 * Creates an intent that can be sent to this service to join two contacts.
803 */
804 public static Intent createJoinContactsIntent(Context context, long contactId1,
805 long contactId2, boolean contactWritable,
806 Class<?> callbackActivity, String callbackAction) {
807 Intent serviceIntent = new Intent(context, ContactSaveService.class);
808 serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS);
809 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1);
810 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2);
811 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_WRITABLE, contactWritable);
812
813 // Callback intent will be invoked by the service once the contacts are joined.
814 Intent callbackIntent = new Intent(context, callbackActivity);
815 callbackIntent.setAction(callbackAction);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800816 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
817
818 return serviceIntent;
819 }
820
821
822 private interface JoinContactQuery {
823 String[] PROJECTION = {
824 RawContacts._ID,
825 RawContacts.CONTACT_ID,
826 RawContacts.NAME_VERIFIED,
827 RawContacts.DISPLAY_NAME_SOURCE,
828 };
829
830 String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?";
831
832 int _ID = 0;
833 int CONTACT_ID = 1;
834 int NAME_VERIFIED = 2;
835 int DISPLAY_NAME_SOURCE = 3;
836 }
837
838 private void joinContacts(Intent intent) {
839 long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1);
840 long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1);
841 boolean writable = intent.getBooleanExtra(EXTRA_CONTACT_WRITABLE, false);
842 if (contactId1 == -1 || contactId2 == -1) {
843 Log.e(TAG, "Invalid arguments for joinContacts request");
844 return;
845 }
846
847 final ContentResolver resolver = getContentResolver();
848
849 // Load raw contact IDs for all raw contacts involved - currently edited and selected
850 // in the join UIs
851 Cursor c = resolver.query(RawContacts.CONTENT_URI,
852 JoinContactQuery.PROJECTION,
853 JoinContactQuery.SELECTION,
854 new String[]{String.valueOf(contactId1), String.valueOf(contactId2)}, null);
855
856 long rawContactIds[];
857 long verifiedNameRawContactId = -1;
858 try {
859 int maxDisplayNameSource = -1;
860 rawContactIds = new long[c.getCount()];
861 for (int i = 0; i < rawContactIds.length; i++) {
862 c.moveToPosition(i);
863 long rawContactId = c.getLong(JoinContactQuery._ID);
864 rawContactIds[i] = rawContactId;
865 int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
866 if (nameSource > maxDisplayNameSource) {
867 maxDisplayNameSource = nameSource;
868 }
869 }
870
871 // Find an appropriate display name for the joined contact:
872 // if should have a higher DisplayNameSource or be the name
873 // of the original contact that we are joining with another.
874 if (writable) {
875 for (int i = 0; i < rawContactIds.length; i++) {
876 c.moveToPosition(i);
877 if (c.getLong(JoinContactQuery.CONTACT_ID) == contactId1) {
878 int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
879 if (nameSource == maxDisplayNameSource
880 && (verifiedNameRawContactId == -1
881 || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0)) {
882 verifiedNameRawContactId = c.getLong(JoinContactQuery._ID);
883 }
884 }
885 }
886 }
887 } finally {
888 c.close();
889 }
890
891 // For each pair of raw contacts, insert an aggregation exception
892 ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
893 for (int i = 0; i < rawContactIds.length; i++) {
894 for (int j = 0; j < rawContactIds.length; j++) {
895 if (i != j) {
896 buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
897 }
898 }
899 }
900
901 // Mark the original contact as "name verified" to make sure that the contact
902 // display name does not change as a result of the join
903 if (verifiedNameRawContactId != -1) {
904 Builder builder = ContentProviderOperation.newUpdate(
905 ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId));
906 builder.withValue(RawContacts.NAME_VERIFIED, 1);
907 operations.add(builder.build());
908 }
909
910 boolean success = false;
911 // Apply all aggregation exceptions as one batch
912 try {
913 resolver.applyBatch(ContactsContract.AUTHORITY, operations);
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800914 showToast(R.string.contactsJoinedMessage);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800915 success = true;
916 } catch (RemoteException e) {
917 Log.e(TAG, "Failed to apply aggregation exception batch", e);
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800918 showToast(R.string.contactSavedErrorToast);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800919 } catch (OperationApplicationException e) {
920 Log.e(TAG, "Failed to apply aggregation exception batch", e);
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800921 showToast(R.string.contactSavedErrorToast);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800922 }
923
924 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
925 if (success) {
926 Uri uri = RawContacts.getContactLookupUri(resolver,
927 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
928 callbackIntent.setData(uri);
929 }
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800930 deliverCallback(callbackIntent);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800931 }
932
933 /**
934 * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation.
935 */
936 private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations,
937 long rawContactId1, long rawContactId2) {
938 Builder builder =
939 ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
940 builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
941 builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
942 builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
943 operations.add(builder.build());
944 }
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800945
946 /**
947 * Shows a toast on the UI thread.
948 */
949 private void showToast(final int message) {
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800950 mMainHandler.post(new Runnable() {
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800951
952 @Override
953 public void run() {
954 Toast.makeText(ContactSaveService.this, message, Toast.LENGTH_LONG).show();
955 }
956 });
957 }
Dmitri Plotnikov3a6a9052011-03-02 10:14:43 -0800958
959 private void deliverCallback(final Intent callbackIntent) {
960 mMainHandler.post(new Runnable() {
961
962 @Override
963 public void run() {
964 deliverCallbackOnUiThread(callbackIntent);
965 }
966 });
967 }
968
969 void deliverCallbackOnUiThread(final Intent callbackIntent) {
970 // TODO: this assumes that if there are multiple instances of the same
971 // activity registered, the last one registered is the one waiting for
972 // the callback. Validity of this assumption needs to be verified.
973 synchronized (sListeners) {
974 for (Listener listener : sListeners) {
975 if (callbackIntent.getComponent().equals(
976 ((Activity) listener).getIntent().getComponent())) {
977 listener.onServiceCompleted(callbackIntent);
978 return;
979 }
980 }
981 }
982 }
Daniel Lehmann173ffe12010-06-14 18:19:10 -0700983}