Renaming a couple of packages
Change-Id: I2c0f86b51baa622df629206f8b79ef1d0df09119
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
new file mode 100644
index 0000000..a926c78
--- /dev/null
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts;
+
+import com.android.contacts.R;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
+
+import android.accounts.Account;
+import android.app.IntentService;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderOperation.Builder;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.AggregationExceptions;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Groups;
+import android.provider.ContactsContract.RawContacts;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * A service responsible for saving changes to the content provider.
+ */
+public class ContactSaveService extends IntentService {
+ private static final String TAG = "ContactSaveService";
+
+ public static final String ACTION_NEW_RAW_CONTACT = "newRawContact";
+
+ public static final String EXTRA_ACCOUNT_NAME = "accountName";
+ public static final String EXTRA_ACCOUNT_TYPE = "accountType";
+ public static final String EXTRA_CONTENT_VALUES = "contentValues";
+ public static final String EXTRA_CALLBACK_INTENT = "callbackIntent";
+
+ public static final String EXTRA_OPERATIONS = "Operations";
+
+ public static final String ACTION_CREATE_GROUP = "createGroup";
+ public static final String ACTION_RENAME_GROUP = "renameGroup";
+ public static final String ACTION_DELETE_GROUP = "deleteGroup";
+ public static final String EXTRA_GROUP_ID = "groupId";
+ public static final String EXTRA_GROUP_LABEL = "groupLabel";
+
+ public static final String ACTION_SET_STARRED = "setStarred";
+ public static final String ACTION_DELETE_CONTACT = "delete";
+ public static final String EXTRA_CONTACT_URI = "contactUri";
+ public static final String EXTRA_STARRED_FLAG = "starred";
+
+ public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary";
+ public static final String ACTION_CLEAR_PRIMARY = "clearPrimary";
+ public static final String EXTRA_DATA_ID = "dataId";
+
+ public static final String ACTION_JOIN_CONTACTS = "joinContacts";
+ public static final String EXTRA_CONTACT_ID1 = "contactId1";
+ public static final String EXTRA_CONTACT_ID2 = "contactId2";
+ public static final String EXTRA_CONTACT_WRITABLE = "contactWritable";
+
+ private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet(
+ Data.MIMETYPE,
+ Data.IS_PRIMARY,
+ Data.DATA1,
+ Data.DATA2,
+ Data.DATA3,
+ Data.DATA4,
+ Data.DATA5,
+ Data.DATA6,
+ Data.DATA7,
+ Data.DATA8,
+ Data.DATA9,
+ Data.DATA10,
+ Data.DATA11,
+ Data.DATA12,
+ Data.DATA13,
+ Data.DATA14,
+ Data.DATA15
+ );
+
+ public ContactSaveService() {
+ super(TAG);
+ setIntentRedelivery(true);
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ String action = intent.getAction();
+ if (ACTION_NEW_RAW_CONTACT.equals(action)) {
+ createRawContact(intent);
+ } else if (ACTION_CREATE_GROUP.equals(action)) {
+ createGroup(intent);
+ } else if (ACTION_RENAME_GROUP.equals(action)) {
+ renameGroup(intent);
+ } else if (ACTION_DELETE_GROUP.equals(action)) {
+ deleteGroup(intent);
+ } else if (ACTION_SET_STARRED.equals(action)) {
+ setStarred(intent);
+ } else if (ACTION_SET_SUPER_PRIMARY.equals(action)) {
+ setSuperPrimary(intent);
+ } else if (ACTION_CLEAR_PRIMARY.equals(action)) {
+ clearPrimary(intent);
+ } else if (ACTION_DELETE_CONTACT.equals(action)) {
+ deleteContact(intent);
+ } else if (ACTION_JOIN_CONTACTS.equals(action)) {
+ joinContacts(intent);
+ }
+ }
+
+ /**
+ * Creates an intent that can be sent to this service to create a new raw contact
+ * using data presented as a set of ContentValues.
+ */
+ public static Intent createNewRawContactIntent(Context context,
+ ArrayList<ContentValues> values, Account account, Class<?> callbackActivity,
+ String callbackAction) {
+ Intent serviceIntent = new Intent(
+ context, ContactSaveService.class);
+ serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT);
+ if (account != null) {
+ serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
+ }
+ serviceIntent.putParcelableArrayListExtra(
+ ContactSaveService.EXTRA_CONTENT_VALUES, values);
+
+ // Callback intent will be invoked by the service once the new contact is
+ // created. The service will put the URI of the new contact as "data" on
+ // the callback intent.
+ Intent callbackIntent = new Intent(context, callbackActivity);
+ callbackIntent.setAction(callbackAction);
+ callbackIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
+ return serviceIntent;
+ }
+
+ private void createRawContact(Intent intent) {
+ String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
+ String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
+ List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES);
+ Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
+
+ ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
+ operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
+ .withValue(RawContacts.ACCOUNT_NAME, accountName)
+ .withValue(RawContacts.ACCOUNT_TYPE, accountType)
+ .build());
+
+ int size = valueList.size();
+ for (int i = 0; i < size; i++) {
+ ContentValues values = valueList.get(i);
+ values.keySet().retainAll(ALLOWED_DATA_COLUMNS);
+ operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
+ .withValueBackReference(Data.RAW_CONTACT_ID, 0)
+ .withValues(values)
+ .build());
+ }
+
+ ContentResolver resolver = getContentResolver();
+ ContentProviderResult[] results;
+ try {
+ results = resolver.applyBatch(ContactsContract.AUTHORITY, operations);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to store new contact", e);
+ }
+
+ Uri rawContactUri = results[0].uri;
+ callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri));
+
+ startActivity(callbackIntent);
+ }
+
+ /**
+ * Creates an intent that can be sent to this service to create a new group.
+ */
+ public static Intent createNewGroupIntent(Context context, Account account, String label,
+ Class<?> callbackActivity, String callbackAction) {
+ Intent serviceIntent = new Intent(context, ContactSaveService.class);
+ serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label);
+
+ // Callback intent will be invoked by the service once the new group is
+ // created. The service will put a group membership row in the extras
+ // of the callback intent.
+ Intent callbackIntent = new Intent(context, callbackActivity);
+ callbackIntent.setAction(callbackAction);
+ callbackIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
+
+ return serviceIntent;
+ }
+
+ private void createGroup(Intent intent) {
+ String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
+ String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
+ String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
+
+ ContentValues values = new ContentValues();
+ values.put(Groups.ACCOUNT_TYPE, accountType);
+ values.put(Groups.ACCOUNT_NAME, accountName);
+ values.put(Groups.TITLE, label);
+
+ Uri groupUri = getContentResolver().insert(Groups.CONTENT_URI, values);
+ if (groupUri == null) {
+ return;
+ }
+
+ values.clear();
+ values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
+ values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri));
+
+ Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
+ callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values));
+
+ startActivity(callbackIntent);
+ }
+
+ /**
+ * Creates an intent that can be sent to this service to rename a group.
+ */
+ public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel) {
+ Intent serviceIntent = new Intent(context, ContactSaveService.class);
+ serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, newLabel);
+ return serviceIntent;
+ }
+
+ private void renameGroup(Intent intent) {
+ long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
+ String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
+
+ if (groupId == -1) {
+ Log.e(TAG, "Invalid arguments for renameGroup request");
+ return;
+ }
+
+ ContentValues values = new ContentValues();
+ values.put(Groups.TITLE, label);
+ getContentResolver().update(
+ ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), values, null, null);
+ }
+
+ /**
+ * Creates an intent that can be sent to this service to delete a group.
+ */
+ public static Intent createGroupDeletionIntent(Context context, long groupId) {
+ Intent serviceIntent = new Intent(context, ContactSaveService.class);
+ serviceIntent.setAction(ContactSaveService.ACTION_DELETE_GROUP);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId);
+ return serviceIntent;
+ }
+
+ private void deleteGroup(Intent intent) {
+ long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1);
+ if (groupId == -1) {
+ Log.e(TAG, "Invalid arguments for deleteGroup request");
+ return;
+ }
+
+ getContentResolver().delete(
+ ContentUris.withAppendedId(Groups.CONTENT_URI, groupId), null, null);
+ }
+
+ /**
+ * Creates an intent that can be sent to this service to star or un-star a contact.
+ */
+ public static Intent createSetStarredIntent(Context context, Uri contactUri, boolean value) {
+ Intent serviceIntent = new Intent(context, ContactSaveService.class);
+ serviceIntent.setAction(ContactSaveService.ACTION_SET_STARRED);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_STARRED_FLAG, value);
+
+ return serviceIntent;
+ }
+
+ private void setStarred(Intent intent) {
+ Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
+ boolean value = intent.getBooleanExtra(EXTRA_STARRED_FLAG, false);
+ if (contactUri == null) {
+ Log.e(TAG, "Invalid arguments for setStarred request");
+ return;
+ }
+
+ final ContentValues values = new ContentValues(1);
+ values.put(Contacts.STARRED, value);
+ getContentResolver().update(contactUri, values, null, null);
+ }
+
+ /**
+ * Creates an intent that sets the selected data item as super primary (default)
+ */
+ public static Intent createSetSuperPrimaryIntent(Context context, long dataId) {
+ Intent serviceIntent = new Intent(context, ContactSaveService.class);
+ serviceIntent.setAction(ContactSaveService.ACTION_SET_SUPER_PRIMARY);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
+ return serviceIntent;
+ }
+
+ private void setSuperPrimary(Intent intent) {
+ long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
+ if (dataId == -1) {
+ Log.e(TAG, "Invalid arguments for setSuperPrimary request");
+ return;
+ }
+
+ // Update the primary values in the data record.
+ ContentValues values = new ContentValues(1);
+ values.put(Data.IS_SUPER_PRIMARY, 1);
+ values.put(Data.IS_PRIMARY, 1);
+
+ getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
+ values, null, null);
+ }
+
+ /**
+ * Creates an intent that clears the primary flag of all data items that belong to the same
+ * raw_contact as the given data item. Will only clear, if the data item was primary before
+ * this call
+ */
+ public static Intent createClearPrimaryIntent(Context context, long dataId) {
+ Intent serviceIntent = new Intent(context, ContactSaveService.class);
+ serviceIntent.setAction(ContactSaveService.ACTION_CLEAR_PRIMARY);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_DATA_ID, dataId);
+ return serviceIntent;
+ }
+
+ private void clearPrimary(Intent intent) {
+ long dataId = intent.getLongExtra(EXTRA_DATA_ID, -1);
+ if (dataId == -1) {
+ Log.e(TAG, "Invalid arguments for clearPrimary request");
+ return;
+ }
+
+ // Update the primary values in the data record.
+ ContentValues values = new ContentValues(1);
+ values.put(Data.IS_SUPER_PRIMARY, 0);
+ values.put(Data.IS_PRIMARY, 0);
+
+ getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
+ values, null, null);
+ }
+
+ /**
+ * Creates an intent that can be sent to this service to delete a contact.
+ */
+ public static Intent createDeleteContactIntent(Context context, Uri contactUri) {
+ Intent serviceIntent = new Intent(context, ContactSaveService.class);
+ serviceIntent.setAction(ContactSaveService.ACTION_DELETE_CONTACT);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_URI, contactUri);
+ return serviceIntent;
+ }
+
+ private void deleteContact(Intent intent) {
+ Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
+ if (contactUri == null) {
+ Log.e(TAG, "Invalid arguments for deleteContact request");
+ return;
+ }
+
+ getContentResolver().delete(contactUri, null, null);
+ }
+
+ /**
+ * Creates an intent that can be sent to this service to join two contacts.
+ */
+ public static Intent createJoinContactsIntent(Context context, long contactId1,
+ long contactId2, boolean contactWritable,
+ Class<?> callbackActivity, String callbackAction) {
+ Intent serviceIntent = new Intent(context, ContactSaveService.class);
+ serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_WRITABLE, contactWritable);
+
+ // Callback intent will be invoked by the service once the contacts are joined.
+ Intent callbackIntent = new Intent(context, callbackActivity);
+ callbackIntent.setAction(callbackAction);
+ callbackIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
+
+ return serviceIntent;
+ }
+
+
+ private interface JoinContactQuery {
+ String[] PROJECTION = {
+ RawContacts._ID,
+ RawContacts.CONTACT_ID,
+ RawContacts.NAME_VERIFIED,
+ RawContacts.DISPLAY_NAME_SOURCE,
+ };
+
+ String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?";
+
+ int _ID = 0;
+ int CONTACT_ID = 1;
+ int NAME_VERIFIED = 2;
+ int DISPLAY_NAME_SOURCE = 3;
+ }
+
+ private void joinContacts(Intent intent) {
+ long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1);
+ long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1);
+ boolean writable = intent.getBooleanExtra(EXTRA_CONTACT_WRITABLE, false);
+ if (contactId1 == -1 || contactId2 == -1) {
+ Log.e(TAG, "Invalid arguments for joinContacts request");
+ return;
+ }
+
+ final ContentResolver resolver = getContentResolver();
+
+ // Load raw contact IDs for all raw contacts involved - currently edited and selected
+ // in the join UIs
+ Cursor c = resolver.query(RawContacts.CONTENT_URI,
+ JoinContactQuery.PROJECTION,
+ JoinContactQuery.SELECTION,
+ new String[]{String.valueOf(contactId1), String.valueOf(contactId2)}, null);
+
+ long rawContactIds[];
+ long verifiedNameRawContactId = -1;
+ try {
+ int maxDisplayNameSource = -1;
+ rawContactIds = new long[c.getCount()];
+ for (int i = 0; i < rawContactIds.length; i++) {
+ c.moveToPosition(i);
+ long rawContactId = c.getLong(JoinContactQuery._ID);
+ rawContactIds[i] = rawContactId;
+ int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
+ if (nameSource > maxDisplayNameSource) {
+ maxDisplayNameSource = nameSource;
+ }
+ }
+
+ // Find an appropriate display name for the joined contact:
+ // if should have a higher DisplayNameSource or be the name
+ // of the original contact that we are joining with another.
+ if (writable) {
+ for (int i = 0; i < rawContactIds.length; i++) {
+ c.moveToPosition(i);
+ if (c.getLong(JoinContactQuery.CONTACT_ID) == contactId1) {
+ int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
+ if (nameSource == maxDisplayNameSource
+ && (verifiedNameRawContactId == -1
+ || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0)) {
+ verifiedNameRawContactId = c.getLong(JoinContactQuery._ID);
+ }
+ }
+ }
+ }
+ } finally {
+ c.close();
+ }
+
+ // For each pair of raw contacts, insert an aggregation exception
+ ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
+ for (int i = 0; i < rawContactIds.length; i++) {
+ for (int j = 0; j < rawContactIds.length; j++) {
+ if (i != j) {
+ buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
+ }
+ }
+ }
+
+ // Mark the original contact as "name verified" to make sure that the contact
+ // display name does not change as a result of the join
+ if (verifiedNameRawContactId != -1) {
+ Builder builder = ContentProviderOperation.newUpdate(
+ ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId));
+ builder.withValue(RawContacts.NAME_VERIFIED, 1);
+ operations.add(builder.build());
+ }
+
+ boolean success = false;
+ // Apply all aggregation exceptions as one batch
+ try {
+ resolver.applyBatch(ContactsContract.AUTHORITY, operations);
+ Toast.makeText(this, R.string.contactsJoinedMessage, Toast.LENGTH_LONG).show();
+ success = true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to apply aggregation exception batch", e);
+ Toast.makeText(this, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
+ } catch (OperationApplicationException e) {
+ Log.e(TAG, "Failed to apply aggregation exception batch", e);
+ Toast.makeText(this, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
+ }
+
+ Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
+ if (success) {
+ Uri uri = RawContacts.getContactLookupUri(resolver,
+ ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
+ callbackIntent.setData(uri);
+ }
+ startActivity(callbackIntent);
+ }
+
+ /**
+ * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation.
+ */
+ private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations,
+ long rawContactId1, long rawContactId2) {
+ Builder builder =
+ ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
+ builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
+ builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
+ builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
+ operations.add(builder.build());
+ }
+}