blob: a926c7809ab3639e85f624b12fc6e3d0c29f4862 [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 Plotnikov2b46f032010-11-29 16:41:43 -080036import android.os.RemoteException;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070037import android.provider.ContactsContract;
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080038import android.provider.ContactsContract.AggregationExceptions;
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -080039import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -080040import android.provider.ContactsContract.Contacts;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070041import android.provider.ContactsContract.Data;
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -080042import android.provider.ContactsContract.Groups;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070043import android.provider.ContactsContract.RawContacts;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070044import android.util.Log;
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080045import android.widget.Toast;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070046
47import java.util.ArrayList;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070048import java.util.HashSet;
49import java.util.List;
Daniel Lehmann173ffe12010-06-14 18:19:10 -070050
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080051/**
52 * A service responsible for saving changes to the content provider.
53 */
Daniel Lehmann173ffe12010-06-14 18:19:10 -070054public class ContactSaveService extends IntentService {
55 private static final String TAG = "ContactSaveService";
56
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070057 public static final String ACTION_NEW_RAW_CONTACT = "newRawContact";
58
59 public static final String EXTRA_ACCOUNT_NAME = "accountName";
60 public static final String EXTRA_ACCOUNT_TYPE = "accountType";
61 public static final String EXTRA_CONTENT_VALUES = "contentValues";
62 public static final String EXTRA_CALLBACK_INTENT = "callbackIntent";
63
Daniel Lehmann173ffe12010-06-14 18:19:10 -070064 public static final String EXTRA_OPERATIONS = "Operations";
65
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -080066 public static final String ACTION_CREATE_GROUP = "createGroup";
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -080067 public static final String ACTION_RENAME_GROUP = "renameGroup";
68 public static final String ACTION_DELETE_GROUP = "deleteGroup";
69 public static final String EXTRA_GROUP_ID = "groupId";
70 public static final String EXTRA_GROUP_LABEL = "groupLabel";
71
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -080072 public static final String ACTION_SET_STARRED = "setStarred";
Dmitri Plotnikov7d8cabb2010-11-24 17:40:01 -080073 public static final String ACTION_DELETE_CONTACT = "delete";
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -080074 public static final String EXTRA_CONTACT_URI = "contactUri";
75 public static final String EXTRA_STARRED_FLAG = "starred";
76
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -080077 public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary";
78 public static final String ACTION_CLEAR_PRIMARY = "clearPrimary";
79 public static final String EXTRA_DATA_ID = "dataId";
80
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -080081 public static final String ACTION_JOIN_CONTACTS = "joinContacts";
82 public static final String EXTRA_CONTACT_ID1 = "contactId1";
83 public static final String EXTRA_CONTACT_ID2 = "contactId2";
84 public static final String EXTRA_CONTACT_WRITABLE = "contactWritable";
85
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070086 private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet(
87 Data.MIMETYPE,
88 Data.IS_PRIMARY,
89 Data.DATA1,
90 Data.DATA2,
91 Data.DATA3,
92 Data.DATA4,
93 Data.DATA5,
94 Data.DATA6,
95 Data.DATA7,
96 Data.DATA8,
97 Data.DATA9,
98 Data.DATA10,
99 Data.DATA11,
100 Data.DATA12,
101 Data.DATA13,
102 Data.DATA14,
103 Data.DATA15
104 );
105
Daniel Lehmann173ffe12010-06-14 18:19:10 -0700106 public ContactSaveService() {
107 super(TAG);
108 setIntentRedelivery(true);
109 }
110
111 @Override
112 protected void onHandleIntent(Intent intent) {
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700113 String action = intent.getAction();
114 if (ACTION_NEW_RAW_CONTACT.equals(action)) {
115 createRawContact(intent);
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800116 } else if (ACTION_CREATE_GROUP.equals(action)) {
117 createGroup(intent);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800118 } else if (ACTION_RENAME_GROUP.equals(action)) {
119 renameGroup(intent);
120 } else if (ACTION_DELETE_GROUP.equals(action)) {
121 deleteGroup(intent);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800122 } else if (ACTION_SET_STARRED.equals(action)) {
123 setStarred(intent);
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -0800124 } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) {
125 setSuperPrimary(intent);
126 } else if (ACTION_CLEAR_PRIMARY.equals(action)) {
127 clearPrimary(intent);
Dmitri Plotnikov7d8cabb2010-11-24 17:40:01 -0800128 } else if (ACTION_DELETE_CONTACT.equals(action)) {
129 deleteContact(intent);
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800130 } else if (ACTION_JOIN_CONTACTS.equals(action)) {
131 joinContacts(intent);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700132 }
133 }
134
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800135 /**
136 * Creates an intent that can be sent to this service to create a new raw contact
137 * using data presented as a set of ContentValues.
138 */
139 public static Intent createNewRawContactIntent(Context context,
140 ArrayList<ContentValues> values, Account account, Class<?> callbackActivity,
141 String callbackAction) {
142 Intent serviceIntent = new Intent(
143 context, ContactSaveService.class);
144 serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT);
145 if (account != null) {
146 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
147 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
148 }
149 serviceIntent.putParcelableArrayListExtra(
150 ContactSaveService.EXTRA_CONTENT_VALUES, values);
151
152 // Callback intent will be invoked by the service once the new contact is
153 // created. The service will put the URI of the new contact as "data" on
154 // the callback intent.
155 Intent callbackIntent = new Intent(context, callbackActivity);
156 callbackIntent.setAction(callbackAction);
157 callbackIntent.setFlags(
158 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
159 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
160 return serviceIntent;
161 }
162
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700163 private void createRawContact(Intent intent) {
164 String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
165 String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
166 List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES);
167 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
168
169 ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
170 operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
171 .withValue(RawContacts.ACCOUNT_NAME, accountName)
172 .withValue(RawContacts.ACCOUNT_TYPE, accountType)
173 .build());
174
175 int size = valueList.size();
176 for (int i = 0; i < size; i++) {
177 ContentValues values = valueList.get(i);
178 values.keySet().retainAll(ALLOWED_DATA_COLUMNS);
179 operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
180 .withValueBackReference(Data.RAW_CONTACT_ID, 0)
181 .withValues(values)
182 .build());
183 }
184
185 ContentResolver resolver = getContentResolver();
186 ContentProviderResult[] results;
187 try {
188 results = resolver.applyBatch(ContactsContract.AUTHORITY, operations);
189 } catch (Exception e) {
190 throw new RuntimeException("Failed to store new contact", e);
191 }
192
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700193 Uri rawContactUri = results[0].uri;
194 callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri));
195
196 startActivity(callbackIntent);
197 }
198
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700199 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800200 * Creates an intent that can be sent to this service to create a new group.
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700201 */
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800202 public static Intent createNewGroupIntent(Context context, Account account, String label,
203 Class<?> callbackActivity, String callbackAction) {
204 Intent serviceIntent = new Intent(context, ContactSaveService.class);
205 serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP);
206 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
207 serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
208 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700209
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800210 // Callback intent will be invoked by the service once the new group is
211 // created. The service will put a group membership row in the extras
212 // of the callback intent.
213 Intent callbackIntent = new Intent(context, callbackActivity);
214 callbackIntent.setAction(callbackAction);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700215 callbackIntent.setFlags(
216 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
217 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800218
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700219 return serviceIntent;
220 }
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800221
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800222 private void createGroup(Intent intent) {
223 String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
224 String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
225 String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
226
227 ContentValues values = new ContentValues();
228 values.put(Groups.ACCOUNT_TYPE, accountType);
229 values.put(Groups.ACCOUNT_NAME, accountName);
230 values.put(Groups.TITLE, label);
231
232 Uri groupUri = getContentResolver().insert(Groups.CONTENT_URI, values);
233 if (groupUri == null) {
234 return;
235 }
236
237 values.clear();
238 values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
239 values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri));
240
241 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
242 callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values));
243
244 startActivity(callbackIntent);
245 }
246
247 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800248 * Creates an intent that can be sent to this service to rename a group.
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800249 */
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800250 public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel) {
251 Intent serviceIntent = new Intent(context, ContactSaveService.class);
252 serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP);
253 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
254 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800255 return serviceIntent;
256 }
257
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800258 private void renameGroup(Intent intent) {
259 long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
260 String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
261
262 if (groupId == -1) {
263 Log.e(TAG, "Invalid arguments for renameGroup request");
264 return;
265 }
266
267 ContentValues values = new ContentValues();
268 values.put(Groups.TITLE, label);
269 getContentResolver().update(
270 ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), values, null, null);
271 }
272
273 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800274 * Creates an intent that can be sent to this service to delete a group.
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800275 */
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800276 public static Intent createGroupDeletionIntent(Context context, long groupId) {
277 Intent serviceIntent = new Intent(context, ContactSaveService.class);
278 serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800279 serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800280 return serviceIntent;
281 }
282
283 private void deleteGroup(Intent intent) {
284 long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
285 if (groupId == -1) {
286 Log.e(TAG, "Invalid arguments for deleteGroup request");
287 return;
288 }
289
290 getContentResolver().delete(
291 ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null);
292 }
293
294 /**
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800295 * Creates an intent that can be sent to this service to star or un-star a contact.
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800296 */
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800297 public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) {
298 Intent serviceIntent = new Intent(context, ContactSaveService.class);
299 serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED);
300 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
301 serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value);
302
Dmitri Plotnikove898a9f2010-11-18 16:58:25 -0800303 return serviceIntent;
304 }
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800305
306 private void setStarred(Intent intent) {
307 Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
308 boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false);
309 if (contactUri == null) {
310 Log.e(TAG, "Invalid arguments for setStarred request");
311 return;
312 }
313
314 final ContentValues values = new ContentValues(1);
315 values.put(Contacts.STARRED, value);
316 getContentResolver().update(contactUri, values, null, null);
Dmitri Plotnikov9d730dd2010-11-24 13:22:23 -0800317 }
Daniel Lehmann0f78e8b2010-11-24 17:32:03 -0800318
319 /**
320 * Creates an intent that sets the selected data item as super primary (default)
321 */
322 public static Intent createSetSuperPrimaryIntent(Context context, long dataId) {
323 Intent serviceIntent = new Intent(context, ContactSaveService.class);
324 serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY);
325 serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
326 return serviceIntent;
327 }
328
329 private void setSuperPrimary(Intent intent) {
330 long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
331 if (dataId == -1) {
332 Log.e(TAG, "Invalid arguments for setSuperPrimary request");
333 return;
334 }
335
336 // Update the primary values in the data record.
337 ContentValues values = new ContentValues(1);
338 values.put(Data.IS_SUPER_PRIMARY, 1);
339 values.put(Data.IS_PRIMARY, 1);
340
341 getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
342 values, null, null);
343 }
344
345 /**
346 * Creates an intent that clears the primary flag of all data items that belong to the same
347 * raw_contact as the given data item. Will only clear, if the data item was primary before
348 * this call
349 */
350 public static Intent createClearPrimaryIntent(Context context, long dataId) {
351 Intent serviceIntent = new Intent(context, ContactSaveService.class);
352 serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY);
353 serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
354 return serviceIntent;
355 }
356
357 private void clearPrimary(Intent intent) {
358 long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
359 if (dataId == -1) {
360 Log.e(TAG, "Invalid arguments for clearPrimary request");
361 return;
362 }
363
364 // Update the primary values in the data record.
365 ContentValues values = new ContentValues(1);
366 values.put(Data.IS_SUPER_PRIMARY, 0);
367 values.put(Data.IS_PRIMARY, 0);
368
369 getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
370 values, null, null);
371 }
Dmitri Plotnikov7d8cabb2010-11-24 17:40:01 -0800372
373 /**
374 * Creates an intent that can be sent to this service to delete a contact.
375 */
376 public static Intent createDeleteContactIntent(Context context, Uri contactUri) {
377 Intent serviceIntent = new Intent(context, ContactSaveService.class);
378 serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT);
379 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
380 return serviceIntent;
381 }
382
383 private void deleteContact(Intent intent) {
384 Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
385 if (contactUri == null) {
386 Log.e(TAG, "Invalid arguments for deleteContact request");
387 return;
388 }
389
390 getContentResolver().delete(contactUri, null, null);
391 }
Dmitri Plotnikov2b46f032010-11-29 16:41:43 -0800392
393 /**
394 * Creates an intent that can be sent to this service to join two contacts.
395 */
396 public static Intent createJoinContactsIntent(Context context, long contactId1,
397 long contactId2, boolean contactWritable,
398 Class<?> callbackActivity, String callbackAction) {
399 Intent serviceIntent = new Intent(context, ContactSaveService.class);
400 serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS);
401 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1);
402 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2);
403 serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_WRITABLE, contactWritable);
404
405 // Callback intent will be invoked by the service once the contacts are joined.
406 Intent callbackIntent = new Intent(context, callbackActivity);
407 callbackIntent.setAction(callbackAction);
408 callbackIntent.setFlags(
409 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
410 serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
411
412 return serviceIntent;
413 }
414
415
416 private interface JoinContactQuery {
417 String[] PROJECTION = {
418 RawContacts._ID,
419 RawContacts.CONTACT_ID,
420 RawContacts.NAME_VERIFIED,
421 RawContacts.DISPLAY_NAME_SOURCE,
422 };
423
424 String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?";
425
426 int _ID = 0;
427 int CONTACT_ID = 1;
428 int NAME_VERIFIED = 2;
429 int DISPLAY_NAME_SOURCE = 3;
430 }
431
432 private void joinContacts(Intent intent) {
433 long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1);
434 long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1);
435 boolean writable = intent.getBooleanExtra(EXTRA_CONTACT_WRITABLE, false);
436 if (contactId1 == -1 || contactId2 == -1) {
437 Log.e(TAG, "Invalid arguments for joinContacts request");
438 return;
439 }
440
441 final ContentResolver resolver = getContentResolver();
442
443 // Load raw contact IDs for all raw contacts involved - currently edited and selected
444 // in the join UIs
445 Cursor c = resolver.query(RawContacts.CONTENT_URI,
446 JoinContactQuery.PROJECTION,
447 JoinContactQuery.SELECTION,
448 new String[]{String.valueOf(contactId1), String.valueOf(contactId2)}, null);
449
450 long rawContactIds[];
451 long verifiedNameRawContactId = -1;
452 try {
453 int maxDisplayNameSource = -1;
454 rawContactIds = new long[c.getCount()];
455 for (int i = 0; i < rawContactIds.length; i++) {
456 c.moveToPosition(i);
457 long rawContactId = c.getLong(JoinContactQuery._ID);
458 rawContactIds[i] = rawContactId;
459 int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
460 if (nameSource > maxDisplayNameSource) {
461 maxDisplayNameSource = nameSource;
462 }
463 }
464
465 // Find an appropriate display name for the joined contact:
466 // if should have a higher DisplayNameSource or be the name
467 // of the original contact that we are joining with another.
468 if (writable) {
469 for (int i = 0; i < rawContactIds.length; i++) {
470 c.moveToPosition(i);
471 if (c.getLong(JoinContactQuery.CONTACT_ID) == contactId1) {
472 int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
473 if (nameSource == maxDisplayNameSource
474 && (verifiedNameRawContactId == -1
475 || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0)) {
476 verifiedNameRawContactId = c.getLong(JoinContactQuery._ID);
477 }
478 }
479 }
480 }
481 } finally {
482 c.close();
483 }
484
485 // For each pair of raw contacts, insert an aggregation exception
486 ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
487 for (int i = 0; i < rawContactIds.length; i++) {
488 for (int j = 0; j < rawContactIds.length; j++) {
489 if (i != j) {
490 buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
491 }
492 }
493 }
494
495 // Mark the original contact as "name verified" to make sure that the contact
496 // display name does not change as a result of the join
497 if (verifiedNameRawContactId != -1) {
498 Builder builder = ContentProviderOperation.newUpdate(
499 ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId));
500 builder.withValue(RawContacts.NAME_VERIFIED, 1);
501 operations.add(builder.build());
502 }
503
504 boolean success = false;
505 // Apply all aggregation exceptions as one batch
506 try {
507 resolver.applyBatch(ContactsContract.AUTHORITY, operations);
508 Toast.makeText(this, R.string.contactsJoinedMessage, Toast.LENGTH_LONG).show();
509 success = true;
510 } catch (RemoteException e) {
511 Log.e(TAG, "Failed to apply aggregation exception batch", e);
512 Toast.makeText(this, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
513 } catch (OperationApplicationException e) {
514 Log.e(TAG, "Failed to apply aggregation exception batch", e);
515 Toast.makeText(this, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
516 }
517
518 Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
519 if (success) {
520 Uri uri = RawContacts.getContactLookupUri(resolver,
521 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
522 callbackIntent.setData(uri);
523 }
524 startActivity(callbackIntent);
525 }
526
527 /**
528 * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation.
529 */
530 private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations,
531 long rawContactId1, long rawContactId2) {
532 Builder builder =
533 ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
534 builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
535 builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
536 builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
537 operations.add(builder.build());
538 }
Daniel Lehmann173ffe12010-06-14 18:19:10 -0700539}