blob: c07dd6834ade7887442570d0bda610e5b79075c8 [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 Plotnikov2b46f032010-11-29 16:41:43 -080019import com.android.contacts.R;
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -080020import com.google.android.collect.Lists;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070021import com.google.android.collect.Sets;
22
23import android.accounts.Account;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070024import android.app.IntentService;
25import android.content.ContentProviderOperation;
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080026import android.content.ContentProviderOperation.Builder;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070027import android.content.ContentProviderResult;
28import android.content.ContentResolver;
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -080029import android.content.ContentUris;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070030import android.content.ContentValues;
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -080031import android.content.Context;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070032import android.content.Intent;
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080033import android.content.OperationApplicationException;
34import android.database.Cursor;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070035import android.net.Uri;
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -080036import android.os.Handler;
37import android.os.Looper;
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080038import android.os.RemoteException;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070039import android.provider.ContactsContract;
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080040import android.provider.ContactsContract.AggregationExceptions;
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -080041import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -080042import android.provider.ContactsContract.Contacts;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070043import android.provider.ContactsContract.Data;
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -080044import android.provider.ContactsContract.Groups;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070045import android.provider.ContactsContract.RawContacts;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070046import android.util.Log;
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080047import android.widget.Toast;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070048
49import java.util.ArrayList;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070050import java.util.HashSet;
51import java.util.List;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070052
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080053/**
54 * A service responsible for saving changes to the content provider.
55 */
Daniel Lehmann173ffe12010-06-14 18:19:10 -070056public class ContactSaveService extends IntentService {
57 private static final String TAG = "ContactSaveService";
58
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070059 public static final String ACTION_NEW_RAW_CONTACT = "newRawContact";
60
61 public static final String EXTRA_ACCOUNT_NAME = "accountName";
62 public static final String EXTRA_ACCOUNT_TYPE = "accountType";
63 public static final String EXTRA_CONTENT_VALUES = "contentValues";
64 public static final String EXTRA_CALLBACK_INTENT = "callbackIntent";
65
Daniel Lehmann173ffe12010-06-14 18:19:10 -070066 public static final String EXTRA_OPERATIONS = "Operations";
67
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -080068 public static final String ACTION_CREATE_GROUP = "createGroup";
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -080069 public static final String ACTION_RENAME_GROUP = "renameGroup";
70 public static final String ACTION_DELETE_GROUP = "deleteGroup";
71 public static final String EXTRA_GROUP_ID = "groupId";
72 public static final String EXTRA_GROUP_LABEL = "groupLabel";
73
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -080074 public static final String ACTION_SET_STARRED = "setStarred";
Dmitri Plotnikov7d8cabb2010-11-24 17:40:01 -080075 public static final String ACTION_DELETE_CONTACT = "delete";
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -080076 public static final String EXTRA_CONTACT_URI = "contactUri";
77 public static final String EXTRA_STARRED_FLAG = "starred";
78
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -080079 public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary";
80 public static final String ACTION_CLEAR_PRIMARY = "clearPrimary";
81 public static final String EXTRA_DATA_ID = "dataId";
82
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080083 public static final String ACTION_JOIN_CONTACTS = "joinContacts";
84 public static final String EXTRA_CONTACT_ID1 = "contactId1";
85 public static final String EXTRA_CONTACT_ID2 = "contactId2";
86 public static final String EXTRA_CONTACT_WRITABLE = "contactWritable";
87
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070088 private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet(
89 Data.MIMETYPE,
90 Data.IS_PRIMARY,
91 Data.DATA1,
92 Data.DATA2,
93 Data.DATA3,
94 Data.DATA4,
95 Data.DATA5,
96 Data.DATA6,
97 Data.DATA7,
98 Data.DATA8,
99 Data.DATA9,
100 Data.DATA10,
101 Data.DATA11,
102 Data.DATA12,
103 Data.DATA13,
104 Data.DATA14,
105 Data.DATA15
106 );
107
Daniel Lehmann173ffe12010-06-14 18:19:10 -0700108 public ContactSaveService() {
109 super(TAG);
110 setIntentRedelivery(true);
111 }
112
113 @Override
114 protected void onHandleIntent(Intent intent) {
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700115 String action = intent.getAction();
116 if (ACTION_NEW_RAW_CONTACT.equals(action)) {
117 createRawContact(intent);
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800118 } else if (ACTION_CREATE_GROUP.equals(action)) {
119 createGroup(intent);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800120 } else if (ACTION_RENAME_GROUP.equals(action)) {
121 renameGroup(intent);
122 } else if (ACTION_DELETE_GROUP.equals(action)) {
123 deleteGroup(intent);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800124 } else if (ACTION_SET_STARRED.equals(action)) {
125 setStarred(intent);
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -0800126 } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) {
127 setSuperPrimary(intent);
128 } else if (ACTION_CLEAR_PRIMARY.equals(action)) {
129 clearPrimary(intent);
Dmitri Plotnikov7d8cabb2010-11-24 17:40:01 -0800130 } else if (ACTION_DELETE_CONTACT.equals(action)) {
131 deleteContact(intent);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800132 } else if (ACTION_JOIN_CONTACTS.equals(action)) {
133 joinContacts(intent);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700134 }
135 }
136
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800137 /**
138 * Creates an intent that can be sent to this service to create a new raw contact
139 * using data presented as a set of ContentValues.
140 */
141 public static Intent createNewRawContactIntent(Context context,
142 ArrayList<ContentValues> values, Account account, Class<?> callbackActivity,
143 String callbackAction) {
144 Intent serviceIntent = new Intent(
145 context, ContactSaveService.class);
146 serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT);
147 if (account != null) {
148 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
149 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
150 }
151 serviceIntent.putParcelableArrayListExtra(
152 ContactSaveService.EXTRA_CONTENT_VALUES, values);
153
154 // Callback intent will be invoked by the service once the new contact is
155 // created. The service will put the URI of the new contact as "data" on
156 // the callback intent.
157 Intent callbackIntent = new Intent(context, callbackActivity);
158 callbackIntent.setAction(callbackAction);
159 callbackIntent.setFlags(
160 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
161 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
162 return serviceIntent;
163 }
164
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700165 private void createRawContact(Intent intent) {
166 String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
167 String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
168 List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES);
169 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
170
171 ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
172 operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
173 .withValue(RawContacts.ACCOUNT_NAME, accountName)
174 .withValue(RawContacts.ACCOUNT_TYPE, accountType)
175 .build());
176
177 int size = valueList.size();
178 for (int i = 0; i < size; i++) {
179 ContentValues values = valueList.get(i);
180 values.keySet().retainAll(ALLOWED_DATA_COLUMNS);
181 operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
182 .withValueBackReference(Data.RAW_CONTACT_ID, 0)
183 .withValues(values)
184 .build());
185 }
186
187 ContentResolver resolver = getContentResolver();
188 ContentProviderResult[] results;
189 try {
190 results = resolver.applyBatch(ContactsContract.AUTHORITY, operations);
191 } catch (Exception e) {
192 throw new RuntimeException("Failed to store new contact", e);
193 }
194
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700195 Uri rawContactUri = results[0].uri;
196 callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri));
197
198 startActivity(callbackIntent);
199 }
200
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700201 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800202 * Creates an intent that can be sent to this service to create a new group.
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700203 */
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800204 public static Intent createNewGroupIntent(Context context, Account account, String label,
205 Class<?> callbackActivity, String callbackAction) {
206 Intent serviceIntent = new Intent(context, ContactSaveService.class);
207 serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP);
208 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
209 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
210 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700211
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800212 // Callback intent will be invoked by the service once the new group is
213 // created. The service will put a group membership row in the extras
214 // of the callback intent.
215 Intent callbackIntent = new Intent(context, callbackActivity);
216 callbackIntent.setAction(callbackAction);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700217 callbackIntent.setFlags(
218 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
219 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800220
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700221 return serviceIntent;
222 }
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800223
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800224 private void createGroup(Intent intent) {
225 String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
226 String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
227 String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
228
229 ContentValues values = new ContentValues();
230 values.put(Groups.ACCOUNT_TYPE, accountType);
231 values.put(Groups.ACCOUNT_NAME, accountName);
232 values.put(Groups.TITLE, label);
233
234 Uri groupUri = getContentResolver().insert(Groups.CONTENT_URI, values);
235 if (groupUri == null) {
236 return;
237 }
238
239 values.clear();
240 values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
241 values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri));
242
243 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
244 callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values));
245
246 startActivity(callbackIntent);
247 }
248
249 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800250 * Creates an intent that can be sent to this service to rename a group.
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800251 */
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800252 public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel) {
253 Intent serviceIntent = new Intent(context, ContactSaveService.class);
254 serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP);
255 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
256 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800257 return serviceIntent;
258 }
259
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800260 private void renameGroup(Intent intent) {
261 long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
262 String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
263
264 if (groupId == -1) {
265 Log.e(TAG, "Invalid arguments for renameGroup request");
266 return;
267 }
268
269 ContentValues values = new ContentValues();
270 values.put(Groups.TITLE, label);
271 getContentResolver().update(
272 ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), values, null, null);
273 }
274
275 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800276 * Creates an intent that can be sent to this service to delete a group.
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800277 */
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800278 public static Intent createGroupDeletionIntent(Context context, long groupId) {
279 Intent serviceIntent = new Intent(context, ContactSaveService.class);
280 serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800281 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800282 return serviceIntent;
283 }
284
285 private void deleteGroup(Intent intent) {
286 long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
287 if (groupId == -1) {
288 Log.e(TAG, "Invalid arguments for deleteGroup request");
289 return;
290 }
291
292 getContentResolver().delete(
293 ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null);
294 }
295
296 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800297 * Creates an intent that can be sent to this service to star or un-star a contact.
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800298 */
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800299 public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) {
300 Intent serviceIntent = new Intent(context, ContactSaveService.class);
301 serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED);
302 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
303 serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value);
304
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800305 return serviceIntent;
306 }
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800307
308 private void setStarred(Intent intent) {
309 Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
310 boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false);
311 if (contactUri == null) {
312 Log.e(TAG, "Invalid arguments for setStarred request");
313 return;
314 }
315
316 final ContentValues values = new ContentValues(1);
317 values.put(Contacts.STARRED, value);
318 getContentResolver().update(contactUri, values, null, null);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800319 }
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -0800320
321 /**
322 * Creates an intent that sets the selected data item as super primary (default)
323 */
324 public static Intent createSetSuperPrimaryIntent(Context context, long dataId) {
325 Intent serviceIntent = new Intent(context, ContactSaveService.class);
326 serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY);
327 serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
328 return serviceIntent;
329 }
330
331 private void setSuperPrimary(Intent intent) {
332 long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
333 if (dataId == -1) {
334 Log.e(TAG, "Invalid arguments for setSuperPrimary request");
335 return;
336 }
337
338 // Update the primary values in the data record.
339 ContentValues values = new ContentValues(1);
340 values.put(Data.IS_SUPER_PRIMARY, 1);
341 values.put(Data.IS_PRIMARY, 1);
342
343 getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
344 values, null, null);
345 }
346
347 /**
348 * Creates an intent that clears the primary flag of all data items that belong to the same
349 * raw_contact as the given data item. Will only clear, if the data item was primary before
350 * this call
351 */
352 public static Intent createClearPrimaryIntent(Context context, long dataId) {
353 Intent serviceIntent = new Intent(context, ContactSaveService.class);
354 serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY);
355 serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
356 return serviceIntent;
357 }
358
359 private void clearPrimary(Intent intent) {
360 long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
361 if (dataId == -1) {
362 Log.e(TAG, "Invalid arguments for clearPrimary request");
363 return;
364 }
365
366 // Update the primary values in the data record.
367 ContentValues values = new ContentValues(1);
368 values.put(Data.IS_SUPER_PRIMARY, 0);
369 values.put(Data.IS_PRIMARY, 0);
370
371 getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
372 values, null, null);
373 }
Dmitri Plotnikov7d8cabb2010-11-24 17:40:01 -0800374
375 /**
376 * Creates an intent that can be sent to this service to delete a contact.
377 */
378 public static Intent createDeleteContactIntent(Context context, Uri contactUri) {
379 Intent serviceIntent = new Intent(context, ContactSaveService.class);
380 serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT);
381 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
382 return serviceIntent;
383 }
384
385 private void deleteContact(Intent intent) {
386 Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
387 if (contactUri == null) {
388 Log.e(TAG, "Invalid arguments for deleteContact request");
389 return;
390 }
391
392 getContentResolver().delete(contactUri, null, null);
393 }
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800394
395 /**
396 * Creates an intent that can be sent to this service to join two contacts.
397 */
398 public static Intent createJoinContactsIntent(Context context, long contactId1,
399 long contactId2, boolean contactWritable,
400 Class<?> callbackActivity, String callbackAction) {
401 Intent serviceIntent = new Intent(context, ContactSaveService.class);
402 serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS);
403 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1);
404 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2);
405 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_WRITABLE, contactWritable);
406
407 // Callback intent will be invoked by the service once the contacts are joined.
408 Intent callbackIntent = new Intent(context, callbackActivity);
409 callbackIntent.setAction(callbackAction);
410 callbackIntent.setFlags(
411 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
412 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
413
414 return serviceIntent;
415 }
416
417
418 private interface JoinContactQuery {
419 String[] PROJECTION = {
420 RawContacts._ID,
421 RawContacts.CONTACT_ID,
422 RawContacts.NAME_VERIFIED,
423 RawContacts.DISPLAY_NAME_SOURCE,
424 };
425
426 String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?";
427
428 int _ID = 0;
429 int CONTACT_ID = 1;
430 int NAME_VERIFIED = 2;
431 int DISPLAY_NAME_SOURCE = 3;
432 }
433
434 private void joinContacts(Intent intent) {
435 long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1);
436 long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1);
437 boolean writable = intent.getBooleanExtra(EXTRA_CONTACT_WRITABLE, false);
438 if (contactId1 == -1 || contactId2 == -1) {
439 Log.e(TAG, "Invalid arguments for joinContacts request");
440 return;
441 }
442
443 final ContentResolver resolver = getContentResolver();
444
445 // Load raw contact IDs for all raw contacts involved - currently edited and selected
446 // in the join UIs
447 Cursor c = resolver.query(RawContacts.CONTENT_URI,
448 JoinContactQuery.PROJECTION,
449 JoinContactQuery.SELECTION,
450 new String[]{String.valueOf(contactId1), String.valueOf(contactId2)}, null);
451
452 long rawContactIds[];
453 long verifiedNameRawContactId = -1;
454 try {
455 int maxDisplayNameSource = -1;
456 rawContactIds = new long[c.getCount()];
457 for (int i = 0; i < rawContactIds.length; i++) {
458 c.moveToPosition(i);
459 long rawContactId = c.getLong(JoinContactQuery._ID);
460 rawContactIds[i] = rawContactId;
461 int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
462 if (nameSource > maxDisplayNameSource) {
463 maxDisplayNameSource = nameSource;
464 }
465 }
466
467 // Find an appropriate display name for the joined contact:
468 // if should have a higher DisplayNameSource or be the name
469 // of the original contact that we are joining with another.
470 if (writable) {
471 for (int i = 0; i < rawContactIds.length; i++) {
472 c.moveToPosition(i);
473 if (c.getLong(JoinContactQuery.CONTACT_ID) == contactId1) {
474 int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
475 if (nameSource == maxDisplayNameSource
476 && (verifiedNameRawContactId == -1
477 || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0)) {
478 verifiedNameRawContactId = c.getLong(JoinContactQuery._ID);
479 }
480 }
481 }
482 }
483 } finally {
484 c.close();
485 }
486
487 // For each pair of raw contacts, insert an aggregation exception
488 ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
489 for (int i = 0; i < rawContactIds.length; i++) {
490 for (int j = 0; j < rawContactIds.length; j++) {
491 if (i != j) {
492 buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
493 }
494 }
495 }
496
497 // Mark the original contact as "name verified" to make sure that the contact
498 // display name does not change as a result of the join
499 if (verifiedNameRawContactId != -1) {
500 Builder builder = ContentProviderOperation.newUpdate(
501 ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId));
502 builder.withValue(RawContacts.NAME_VERIFIED, 1);
503 operations.add(builder.build());
504 }
505
506 boolean success = false;
507 // Apply all aggregation exceptions as one batch
508 try {
509 resolver.applyBatch(ContactsContract.AUTHORITY, operations);
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800510 showToast(R.string.contactsJoinedMessage);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800511 success = true;
512 } catch (RemoteException e) {
513 Log.e(TAG, "Failed to apply aggregation exception batch", e);
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800514 showToast(R.string.contactSavedErrorToast);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800515 } catch (OperationApplicationException e) {
516 Log.e(TAG, "Failed to apply aggregation exception batch", e);
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800517 showToast(R.string.contactSavedErrorToast);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800518 }
519
520 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
521 if (success) {
522 Uri uri = RawContacts.getContactLookupUri(resolver,
523 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
524 callbackIntent.setData(uri);
525 }
526 startActivity(callbackIntent);
527 }
528
529 /**
530 * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation.
531 */
532 private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations,
533 long rawContactId1, long rawContactId2) {
534 Builder builder =
535 ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
536 builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
537 builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
538 builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
539 operations.add(builder.build());
540 }
Dmitri Plotnikov886d3d62011-01-03 10:08:47 -0800541
542 /**
543 * Shows a toast on the UI thread.
544 */
545 private void showToast(final int message) {
546 new Handler(Looper.getMainLooper()).post(new Runnable() {
547
548 @Override
549 public void run() {
550 Toast.makeText(ContactSaveService.this, message, Toast.LENGTH_LONG).show();
551 }
552 });
553 }
Daniel Lehmann173ffe12010-06-14 18:19:10 -0700554}