Home | History | Annotate | Download | only in platform
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * 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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 package com.example.android.samplesync.platform;
     17 
     18 import com.example.android.samplesync.Constants;
     19 import com.example.android.samplesync.R;
     20 import com.example.android.samplesync.client.RawContact;
     21 
     22 import android.accounts.Account;
     23 import android.content.ContentResolver;
     24 import android.content.ContentUris;
     25 import android.content.ContentValues;
     26 import android.content.Context;
     27 import android.database.Cursor;
     28 import android.net.Uri;
     29 import android.provider.ContactsContract;
     30 import android.provider.ContactsContract.CommonDataKinds.Email;
     31 import android.provider.ContactsContract.CommonDataKinds.Im;
     32 import android.provider.ContactsContract.CommonDataKinds.Phone;
     33 import android.provider.ContactsContract.CommonDataKinds.Photo;
     34 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
     35 import android.provider.ContactsContract.Contacts;
     36 import android.provider.ContactsContract.Data;
     37 import android.provider.ContactsContract.Groups;
     38 import android.provider.ContactsContract.RawContacts;
     39 import android.provider.ContactsContract.Settings;
     40 import android.provider.ContactsContract.StatusUpdates;
     41 import android.util.Log;
     42 
     43 import java.util.ArrayList;
     44 import java.util.List;
     45 
     46 /**
     47  * Class for managing contacts sync related mOperations
     48  */
     49 public class ContactManager {
     50 
     51     /**
     52      * Custom IM protocol used when storing status messages.
     53      */
     54     public static final String CUSTOM_IM_PROTOCOL = "SampleSyncAdapter";
     55 
     56     private static final String TAG = "ContactManager";
     57 
     58     public static final String SAMPLE_GROUP_NAME = "Sample Group";
     59 
     60     public static long ensureSampleGroupExists(Context context, Account account) {
     61         final ContentResolver resolver = context.getContentResolver();
     62 
     63         // Lookup the sample group
     64         long groupId = 0;
     65         final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] { Groups._ID },
     66                 Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=? AND " +
     67                 Groups.TITLE + "=?",
     68                 new String[] { account.name, account.type, SAMPLE_GROUP_NAME }, null);
     69         if (cursor != null) {
     70             try {
     71                 if (cursor.moveToFirst()) {
     72                     groupId = cursor.getLong(0);
     73                 }
     74             } finally {
     75                 cursor.close();
     76             }
     77         }
     78 
     79         if (groupId == 0) {
     80             // Sample group doesn't exist yet, so create it
     81             final ContentValues contentValues = new ContentValues();
     82             contentValues.put(Groups.ACCOUNT_NAME, account.name);
     83             contentValues.put(Groups.ACCOUNT_TYPE, account.type);
     84             contentValues.put(Groups.TITLE, SAMPLE_GROUP_NAME);
     85             contentValues.put(Groups.GROUP_IS_READ_ONLY, true);
     86 
     87             final Uri newGroupUri = resolver.insert(Groups.CONTENT_URI, contentValues);
     88             groupId = ContentUris.parseId(newGroupUri);
     89         }
     90         return groupId;
     91     }
     92 
     93     /**
     94      * Take a list of updated contacts and apply those changes to the
     95      * contacts database. Typically this list of contacts would have been
     96      * returned from the server, and we want to apply those changes locally.
     97      *
     98      * @param context The context of Authenticator Activity
     99      * @param account The username for the account
    100      * @param rawContacts The list of contacts to update
    101      * @param lastSyncMarker The previous server sync-state
    102      * @return the server syncState that should be used in our next
    103      * sync request.
    104      */
    105     public static synchronized long updateContacts(Context context, String account,
    106             List<RawContact> rawContacts, long groupId, long lastSyncMarker) {
    107 
    108         long currentSyncMarker = lastSyncMarker;
    109         final ContentResolver resolver = context.getContentResolver();
    110         final BatchOperation batchOperation = new BatchOperation(context, resolver);
    111         final List<RawContact> newUsers = new ArrayList<RawContact>();
    112 
    113         Log.d(TAG, "In SyncContacts");
    114         for (final RawContact rawContact : rawContacts) {
    115             // The server returns a syncState (x) value with each contact record.
    116             // The syncState is sequential, so higher values represent more recent
    117             // changes than lower values. We keep track of the highest value we
    118             // see, and consider that a "high water mark" for the changes we've
    119             // received from the server.  That way, on our next sync, we can just
    120             // ask for changes that have occurred since that most-recent change.
    121             if (rawContact.getSyncState() > currentSyncMarker) {
    122                 currentSyncMarker = rawContact.getSyncState();
    123             }
    124 
    125             // If the server returned a clientId for this user, then it's likely
    126             // that the user was added here, and was just pushed to the server
    127             // for the first time. In that case, we need to update the main
    128             // row for this contact so that the RawContacts.SOURCE_ID value
    129             // contains the correct serverId.
    130             final long rawContactId;
    131             final boolean updateServerId;
    132             if (rawContact.getRawContactId() > 0) {
    133                 rawContactId = rawContact.getRawContactId();
    134                 updateServerId = true;
    135             } else {
    136                 long serverContactId = rawContact.getServerContactId();
    137                 rawContactId = lookupRawContact(resolver, serverContactId);
    138                 updateServerId = false;
    139             }
    140             if (rawContactId != 0) {
    141                 if (!rawContact.isDeleted()) {
    142                     updateContact(context, resolver, rawContact, updateServerId,
    143                             true, true, true, rawContactId, batchOperation);
    144                 } else {
    145                     deleteContact(context, rawContactId, batchOperation);
    146                 }
    147             } else {
    148                 Log.d(TAG, "In addContact");
    149                 if (!rawContact.isDeleted()) {
    150                     newUsers.add(rawContact);
    151                     addContact(context, account, rawContact, groupId, true, batchOperation);
    152                 }
    153             }
    154             // A sync adapter should batch operations on multiple contacts,
    155             // because it will make a dramatic performance difference.
    156             // (UI updates, etc)
    157             if (batchOperation.size() >= 50) {
    158                 batchOperation.execute();
    159             }
    160         }
    161         batchOperation.execute();
    162 
    163         return currentSyncMarker;
    164     }
    165 
    166     /**
    167      * Return a list of the local contacts that have been marked as
    168      * "dirty", and need syncing to the SampleSync server.
    169      *
    170      * @param context The context of Authenticator Activity
    171      * @param account The account that we're interested in syncing
    172      * @return a list of Users that are considered "dirty"
    173      */
    174     public static List<RawContact> getDirtyContacts(Context context, Account account) {
    175         Log.i(TAG, "*** Looking for local dirty contacts");
    176         List<RawContact> dirtyContacts = new ArrayList<RawContact>();
    177 
    178         final ContentResolver resolver = context.getContentResolver();
    179         final Cursor c = resolver.query(DirtyQuery.CONTENT_URI,
    180                 DirtyQuery.PROJECTION,
    181                 DirtyQuery.SELECTION,
    182                 new String[] {account.name},
    183                 null);
    184         try {
    185             while (c.moveToNext()) {
    186                 final long rawContactId = c.getLong(DirtyQuery.COLUMN_RAW_CONTACT_ID);
    187                 final long serverContactId = c.getLong(DirtyQuery.COLUMN_SERVER_ID);
    188                 final boolean isDirty = "1".equals(c.getString(DirtyQuery.COLUMN_DIRTY));
    189                 final boolean isDeleted = "1".equals(c.getString(DirtyQuery.COLUMN_DELETED));
    190 
    191                 // The system actually keeps track of a change version number for
    192                 // each contact. It may be something you're interested in for your
    193                 // client-server sync protocol. We're not using it in this example,
    194                 // other than to log it.
    195                 final long version = c.getLong(DirtyQuery.COLUMN_VERSION);
    196 
    197                 Log.i(TAG, "Dirty Contact: " + Long.toString(rawContactId));
    198                 Log.i(TAG, "Contact Version: " + Long.toString(version));
    199 
    200                 if (isDeleted) {
    201                     Log.i(TAG, "Contact is marked for deletion");
    202                     RawContact rawContact = RawContact.createDeletedContact(rawContactId,
    203                             serverContactId);
    204                     dirtyContacts.add(rawContact);
    205                 } else if (isDirty) {
    206                     RawContact rawContact = getRawContact(context, rawContactId);
    207                     Log.i(TAG, "Contact Name: " + rawContact.getBestName());
    208                     dirtyContacts.add(rawContact);
    209                 }
    210             }
    211 
    212         } finally {
    213             if (c != null) {
    214                 c.close();
    215             }
    216         }
    217         return dirtyContacts;
    218     }
    219 
    220     /**
    221      * Update the status messages for a list of users.  This is typically called
    222      * for contacts we've just added to the system, since we can't monkey with
    223      * the contact's status until they have a profileId.
    224      *
    225      * @param context The context of Authenticator Activity
    226      * @param rawContacts The list of users we want to update
    227      */
    228     public static void updateStatusMessages(Context context, List<RawContact> rawContacts) {
    229         final ContentResolver resolver = context.getContentResolver();
    230         final BatchOperation batchOperation = new BatchOperation(context, resolver);
    231         for (RawContact rawContact : rawContacts) {
    232             updateContactStatus(context, rawContact, batchOperation);
    233         }
    234         batchOperation.execute();
    235     }
    236 
    237     /**
    238      * After we've finished up a sync operation, we want to clean up the sync-state
    239      * so that we're ready for the next time.  This involves clearing out the 'dirty'
    240      * flag on the synced contacts - but we also have to finish the DELETE operation
    241      * on deleted contacts.  When the user initially deletes them on the client, they're
    242      * marked for deletion - but they're not actually deleted until we delete them
    243      * again, and include the ContactsContract.CALLER_IS_SYNCADAPTER parameter to
    244      * tell the contacts provider that we're really ready to let go of this contact.
    245      *
    246      * @param context The context of Authenticator Activity
    247      * @param dirtyContacts The list of contacts that we're cleaning up
    248      */
    249     public static void clearSyncFlags(Context context, List<RawContact> dirtyContacts) {
    250         Log.i(TAG, "*** Clearing Sync-related Flags");
    251         final ContentResolver resolver = context.getContentResolver();
    252         final BatchOperation batchOperation = new BatchOperation(context, resolver);
    253         for (RawContact rawContact : dirtyContacts) {
    254             if (rawContact.isDeleted()) {
    255                 Log.i(TAG, "Deleting contact: " + Long.toString(rawContact.getRawContactId()));
    256                 deleteContact(context, rawContact.getRawContactId(), batchOperation);
    257             } else if (rawContact.isDirty()) {
    258                 Log.i(TAG, "Clearing dirty flag for: " + rawContact.getBestName());
    259                 clearDirtyFlag(context, rawContact.getRawContactId(), batchOperation);
    260             }
    261         }
    262         batchOperation.execute();
    263     }
    264 
    265     /**
    266      * Adds a single contact to the platform contacts provider.
    267      * This can be used to respond to a new contact found as part
    268      * of sync information returned from the server, or because a
    269      * user added a new contact.
    270      *
    271      * @param context the Authenticator Activity context
    272      * @param accountName the account the contact belongs to
    273      * @param rawContact the sample SyncAdapter User object
    274      * @param groupId the id of the sample group
    275      * @param inSync is the add part of a client-server sync?
    276      * @param batchOperation allow us to batch together multiple operations
    277      *        into a single provider call
    278      */
    279     public static void addContact(Context context, String accountName, RawContact rawContact,
    280             long groupId, boolean inSync, BatchOperation batchOperation) {
    281 
    282         // Put the data in the contacts provider
    283         final ContactOperations contactOp = ContactOperations.createNewContact(
    284                 context, rawContact.getServerContactId(), accountName, inSync, batchOperation);
    285 
    286         contactOp.addName(rawContact.getFullName(), rawContact.getFirstName(),
    287                 rawContact.getLastName())
    288                 .addEmail(rawContact.getEmail())
    289                 .addPhone(rawContact.getCellPhone(), Phone.TYPE_MOBILE)
    290                 .addPhone(rawContact.getHomePhone(), Phone.TYPE_HOME)
    291                 .addPhone(rawContact.getOfficePhone(), Phone.TYPE_WORK)
    292                 .addGroupMembership(groupId)
    293                 .addAvatar(rawContact.getAvatarUrl());
    294 
    295         // If we have a serverId, then go ahead and create our status profile.
    296         // Otherwise skip it - and we'll create it after we sync-up to the
    297         // server later on.
    298         if (rawContact.getServerContactId() > 0) {
    299             contactOp.addProfileAction(rawContact.getServerContactId());
    300         }
    301     }
    302 
    303     /**
    304      * Updates a single contact to the platform contacts provider.
    305      * This method can be used to update a contact from a sync
    306      * operation or as a result of a user editing a contact
    307      * record.
    308      *
    309      * This operation is actually relatively complex.  We query
    310      * the database to find all the rows of info that already
    311      * exist for this Contact. For rows that exist (and thus we're
    312      * modifying existing fields), we create an update operation
    313      * to change that field.  But for fields we're adding, we create
    314      * "add" operations to create new rows for those fields.
    315      *
    316      * @param context the Authenticator Activity context
    317      * @param resolver the ContentResolver to use
    318      * @param rawContact the sample SyncAdapter contact object
    319      * @param updateStatus should we update this user's status
    320      * @param updateAvatar should we update this user's avatar image
    321      * @param inSync is the update part of a client-server sync?
    322      * @param rawContactId the unique Id for this rawContact in contacts
    323      *        provider
    324      * @param batchOperation allow us to batch together multiple operations
    325      *        into a single provider call
    326      */
    327     public static void updateContact(Context context, ContentResolver resolver,
    328         RawContact rawContact, boolean updateServerId, boolean updateStatus, boolean updateAvatar,
    329         boolean inSync, long rawContactId, BatchOperation batchOperation) {
    330 
    331         boolean existingCellPhone = false;
    332         boolean existingHomePhone = false;
    333         boolean existingWorkPhone = false;
    334         boolean existingEmail = false;
    335         boolean existingAvatar = false;
    336 
    337         final Cursor c =
    338                 resolver.query(DataQuery.CONTENT_URI, DataQuery.PROJECTION, DataQuery.SELECTION,
    339                 new String[] {String.valueOf(rawContactId)}, null);
    340         final ContactOperations contactOp =
    341                 ContactOperations.updateExistingContact(context, rawContactId,
    342                 inSync, batchOperation);
    343         try {
    344             // Iterate over the existing rows of data, and update each one
    345             // with the information we received from the server.
    346             while (c.moveToNext()) {
    347                 final long id = c.getLong(DataQuery.COLUMN_ID);
    348                 final String mimeType = c.getString(DataQuery.COLUMN_MIMETYPE);
    349                 final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
    350                 if (mimeType.equals(StructuredName.CONTENT_ITEM_TYPE)) {
    351                     contactOp.updateName(uri,
    352                             c.getString(DataQuery.COLUMN_GIVEN_NAME),
    353                             c.getString(DataQuery.COLUMN_FAMILY_NAME),
    354                             c.getString(DataQuery.COLUMN_FULL_NAME),
    355                             rawContact.getFirstName(),
    356                             rawContact.getLastName(),
    357                             rawContact.getFullName());
    358                 } else if (mimeType.equals(Phone.CONTENT_ITEM_TYPE)) {
    359                     final int type = c.getInt(DataQuery.COLUMN_PHONE_TYPE);
    360                     if (type == Phone.TYPE_MOBILE) {
    361                         existingCellPhone = true;
    362                         contactOp.updatePhone(c.getString(DataQuery.COLUMN_PHONE_NUMBER),
    363                                 rawContact.getCellPhone(), uri);
    364                     } else if (type == Phone.TYPE_HOME) {
    365                         existingHomePhone = true;
    366                         contactOp.updatePhone(c.getString(DataQuery.COLUMN_PHONE_NUMBER),
    367                                 rawContact.getHomePhone(), uri);
    368                     } else if (type == Phone.TYPE_WORK) {
    369                         existingWorkPhone = true;
    370                         contactOp.updatePhone(c.getString(DataQuery.COLUMN_PHONE_NUMBER),
    371                                 rawContact.getOfficePhone(), uri);
    372                     }
    373                 } else if (mimeType.equals(Email.CONTENT_ITEM_TYPE)) {
    374                     existingEmail = true;
    375                     contactOp.updateEmail(rawContact.getEmail(),
    376                             c.getString(DataQuery.COLUMN_EMAIL_ADDRESS), uri);
    377                 } else if (mimeType.equals(Photo.CONTENT_ITEM_TYPE)) {
    378                     existingAvatar = true;
    379                     contactOp.updateAvatar(rawContact.getAvatarUrl(), uri);
    380                 }
    381             } // while
    382         } finally {
    383             c.close();
    384         }
    385 
    386         // Add the cell phone, if present and not updated above
    387         if (!existingCellPhone) {
    388             contactOp.addPhone(rawContact.getCellPhone(), Phone.TYPE_MOBILE);
    389         }
    390         // Add the home phone, if present and not updated above
    391         if (!existingHomePhone) {
    392             contactOp.addPhone(rawContact.getHomePhone(), Phone.TYPE_HOME);
    393         }
    394 
    395         // Add the work phone, if present and not updated above
    396         if (!existingWorkPhone) {
    397             contactOp.addPhone(rawContact.getOfficePhone(), Phone.TYPE_WORK);
    398         }
    399         // Add the email address, if present and not updated above
    400         if (!existingEmail) {
    401             contactOp.addEmail(rawContact.getEmail());
    402         }
    403         // Add the avatar if we didn't update the existing avatar
    404         if (!existingAvatar) {
    405             contactOp.addAvatar(rawContact.getAvatarUrl());
    406         }
    407 
    408         // If we need to update the serverId of the contact record, take
    409         // care of that.  This will happen if the contact is created on the
    410         // client, and then synced to the server. When we get the updated
    411         // record back from the server, we can set the SOURCE_ID property
    412         // on the contact, so we can (in the future) lookup contacts by
    413         // the serverId.
    414         if (updateServerId) {
    415             Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
    416             contactOp.updateServerId(rawContact.getServerContactId(), uri);
    417         }
    418 
    419         // If we don't have a status profile, then create one.  This could
    420         // happen for contacts that were created on the client - we don't
    421         // create the status profile until after the first sync...
    422         final long serverId = rawContact.getServerContactId();
    423         final long profileId = lookupProfile(resolver, serverId);
    424         if (profileId <= 0) {
    425             contactOp.addProfileAction(serverId);
    426         }
    427     }
    428 
    429     /**
    430      * When we first add a sync adapter to the system, the contacts from that
    431      * sync adapter will be hidden unless they're merged/grouped with an existing
    432      * contact.  But typically we want to actually show those contacts, so we
    433      * need to mess with the Settings table to get them to show up.
    434      *
    435      * @param context the Authenticator Activity context
    436      * @param account the Account who's visibility we're changing
    437      * @param visible true if we want the contacts visible, false for hidden
    438      */
    439     public static void setAccountContactsVisibility(Context context, Account account,
    440             boolean visible) {
    441         ContentValues values = new ContentValues();
    442         values.put(RawContacts.ACCOUNT_NAME, account.name);
    443         values.put(RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
    444         values.put(Settings.UNGROUPED_VISIBLE, visible ? 1 : 0);
    445 
    446         context.getContentResolver().insert(Settings.CONTENT_URI, values);
    447     }
    448 
    449     /**
    450      * Return a User object with data extracted from a contact stored
    451      * in the local contacts database.
    452      *
    453      * Because a contact is actually stored over several rows in the
    454      * database, our query will return those multiple rows of information.
    455      * We then iterate over the rows and build the User structure from
    456      * what we find.
    457      *
    458      * @param context the Authenticator Activity context
    459      * @param rawContactId the unique ID for the local contact
    460      * @return a User object containing info on that contact
    461      */
    462     private static RawContact getRawContact(Context context, long rawContactId) {
    463         String firstName = null;
    464         String lastName = null;
    465         String fullName = null;
    466         String cellPhone = null;
    467         String homePhone = null;
    468         String workPhone = null;
    469         String email = null;
    470         long serverId = -1;
    471 
    472         final ContentResolver resolver = context.getContentResolver();
    473         final Cursor c =
    474             resolver.query(DataQuery.CONTENT_URI, DataQuery.PROJECTION, DataQuery.SELECTION,
    475                 new String[] {String.valueOf(rawContactId)}, null);
    476         try {
    477             while (c.moveToNext()) {
    478                 final long id = c.getLong(DataQuery.COLUMN_ID);
    479                 final String mimeType = c.getString(DataQuery.COLUMN_MIMETYPE);
    480                 final long tempServerId = c.getLong(DataQuery.COLUMN_SERVER_ID);
    481                 if (tempServerId > 0) {
    482                     serverId = tempServerId;
    483                 }
    484                 final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
    485                 if (mimeType.equals(StructuredName.CONTENT_ITEM_TYPE)) {
    486                     lastName = c.getString(DataQuery.COLUMN_FAMILY_NAME);
    487                     firstName = c.getString(DataQuery.COLUMN_GIVEN_NAME);
    488                     fullName = c.getString(DataQuery.COLUMN_FULL_NAME);
    489                 } else if (mimeType.equals(Phone.CONTENT_ITEM_TYPE)) {
    490                     final int type = c.getInt(DataQuery.COLUMN_PHONE_TYPE);
    491                     if (type == Phone.TYPE_MOBILE) {
    492                         cellPhone = c.getString(DataQuery.COLUMN_PHONE_NUMBER);
    493                     } else if (type == Phone.TYPE_HOME) {
    494                         homePhone = c.getString(DataQuery.COLUMN_PHONE_NUMBER);
    495                     } else if (type == Phone.TYPE_WORK) {
    496                         workPhone = c.getString(DataQuery.COLUMN_PHONE_NUMBER);
    497                     }
    498                 } else if (mimeType.equals(Email.CONTENT_ITEM_TYPE)) {
    499                     email = c.getString(DataQuery.COLUMN_EMAIL_ADDRESS);
    500                 }
    501             } // while
    502         } finally {
    503             c.close();
    504         }
    505 
    506         // Now that we've extracted all the information we care about,
    507         // create the actual User object.
    508         RawContact rawContact = RawContact.create(fullName, firstName, lastName, cellPhone,
    509                 workPhone, homePhone, email, null, false, rawContactId, serverId);
    510 
    511         return rawContact;
    512     }
    513 
    514     /**
    515      * Update the status message associated with the specified user.  The status
    516      * message would be something that is likely to be used by IM or social
    517      * networking sync providers, and less by a straightforward contact provider.
    518      * But it's a useful demo to see how it's done.
    519      *
    520      * @param context the Authenticator Activity context
    521      * @param rawContact the contact whose status we should update
    522      * @param batchOperation allow us to batch together multiple operations
    523      */
    524     private static void updateContactStatus(Context context, RawContact rawContact,
    525             BatchOperation batchOperation) {
    526         final ContentValues values = new ContentValues();
    527         final ContentResolver resolver = context.getContentResolver();
    528 
    529         final long userId = rawContact.getServerContactId();
    530         final String username = rawContact.getUserName();
    531         final String status = rawContact.getStatus();
    532 
    533         // Look up the user's sample SyncAdapter data row
    534         final long profileId = lookupProfile(resolver, userId);
    535 
    536         // Insert the activity into the stream
    537         if (profileId > 0) {
    538             values.put(StatusUpdates.DATA_ID, profileId);
    539             values.put(StatusUpdates.STATUS, status);
    540             values.put(StatusUpdates.PROTOCOL, Im.PROTOCOL_CUSTOM);
    541             values.put(StatusUpdates.CUSTOM_PROTOCOL, CUSTOM_IM_PROTOCOL);
    542             values.put(StatusUpdates.IM_ACCOUNT, username);
    543             values.put(StatusUpdates.IM_HANDLE, userId);
    544             values.put(StatusUpdates.STATUS_RES_PACKAGE, context.getPackageName());
    545             values.put(StatusUpdates.STATUS_ICON, R.drawable.icon);
    546             values.put(StatusUpdates.STATUS_LABEL, R.string.label);
    547             batchOperation.add(ContactOperations.newInsertCpo(StatusUpdates.CONTENT_URI,
    548                     false, true).withValues(values).build());
    549         }
    550     }
    551 
    552     /**
    553      * Clear the local system 'dirty' flag for a contact.
    554      *
    555      * @param context the Authenticator Activity context
    556      * @param rawContactId the id of the contact update
    557      * @param batchOperation allow us to batch together multiple operations
    558      */
    559     private static void clearDirtyFlag(Context context, long rawContactId,
    560         BatchOperation batchOperation) {
    561         final ContactOperations contactOp =
    562                 ContactOperations.updateExistingContact(context, rawContactId, true,
    563                 batchOperation);
    564 
    565         final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
    566         contactOp.updateDirtyFlag(false, uri);
    567     }
    568 
    569      /**
    570      * Deletes a contact from the platform contacts provider. This method is used
    571      * both for contacts that were deleted locally and then that deletion was synced
    572      * to the server, and for contacts that were deleted on the server and the
    573      * deletion was synced to the client.
    574      *
    575      * @param context the Authenticator Activity context
    576      * @param rawContactId the unique Id for this rawContact in contacts
    577      *        provider
    578      */
    579     private static void deleteContact(Context context, long rawContactId,
    580         BatchOperation batchOperation) {
    581 
    582         batchOperation.add(ContactOperations.newDeleteCpo(
    583                 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
    584                 true, true).build());
    585     }
    586 
    587     /**
    588      * Returns the RawContact id for a sample SyncAdapter contact, or 0 if the
    589      * sample SyncAdapter user isn't found.
    590      *
    591      * @param resolver the content resolver to use
    592      * @param serverContactId the sample SyncAdapter user ID to lookup
    593      * @return the RawContact id, or 0 if not found
    594      */
    595     private static long lookupRawContact(ContentResolver resolver, long serverContactId) {
    596 
    597         long rawContactId = 0;
    598         final Cursor c = resolver.query(
    599                 UserIdQuery.CONTENT_URI,
    600                 UserIdQuery.PROJECTION,
    601                 UserIdQuery.SELECTION,
    602                 new String[] {String.valueOf(serverContactId)},
    603                 null);
    604         try {
    605             if ((c != null) && c.moveToFirst()) {
    606                 rawContactId = c.getLong(UserIdQuery.COLUMN_RAW_CONTACT_ID);
    607             }
    608         } finally {
    609             if (c != null) {
    610                 c.close();
    611             }
    612         }
    613         return rawContactId;
    614     }
    615 
    616     /**
    617      * Returns the Data id for a sample SyncAdapter contact's profile row, or 0
    618      * if the sample SyncAdapter user isn't found.
    619      *
    620      * @param resolver a content resolver
    621      * @param userId the sample SyncAdapter user ID to lookup
    622      * @return the profile Data row id, or 0 if not found
    623      */
    624     private static long lookupProfile(ContentResolver resolver, long userId) {
    625 
    626         long profileId = 0;
    627         final Cursor c =
    628             resolver.query(Data.CONTENT_URI, ProfileQuery.PROJECTION, ProfileQuery.SELECTION,
    629                 new String[] {String.valueOf(userId)}, null);
    630         try {
    631             if ((c != null) && c.moveToFirst()) {
    632                 profileId = c.getLong(ProfileQuery.COLUMN_ID);
    633             }
    634         } finally {
    635             if (c != null) {
    636                 c.close();
    637             }
    638         }
    639         return profileId;
    640     }
    641 
    642     final public static class EditorQuery {
    643 
    644         private EditorQuery() {
    645         }
    646 
    647         public static final String[] PROJECTION = new String[] {
    648             RawContacts.ACCOUNT_NAME,
    649             Data._ID,
    650             RawContacts.Entity.DATA_ID,
    651             Data.MIMETYPE,
    652             Data.DATA1,
    653             Data.DATA2,
    654             Data.DATA3,
    655             Data.DATA15,
    656             Data.SYNC1
    657             };
    658 
    659         public static final int COLUMN_ACCOUNT_NAME = 0;
    660         public static final int COLUMN_RAW_CONTACT_ID = 1;
    661         public static final int COLUMN_DATA_ID = 2;
    662         public static final int COLUMN_MIMETYPE = 3;
    663         public static final int COLUMN_DATA1 = 4;
    664         public static final int COLUMN_DATA2 = 5;
    665         public static final int COLUMN_DATA3 = 6;
    666         public static final int COLUMN_DATA15 = 7;
    667         public static final int COLUMN_SYNC1 = 8;
    668 
    669         public static final int COLUMN_PHONE_NUMBER = COLUMN_DATA1;
    670         public static final int COLUMN_PHONE_TYPE = COLUMN_DATA2;
    671         public static final int COLUMN_EMAIL_ADDRESS = COLUMN_DATA1;
    672         public static final int COLUMN_EMAIL_TYPE = COLUMN_DATA2;
    673         public static final int COLUMN_FULL_NAME = COLUMN_DATA1;
    674         public static final int COLUMN_GIVEN_NAME = COLUMN_DATA2;
    675         public static final int COLUMN_FAMILY_NAME = COLUMN_DATA3;
    676         public static final int COLUMN_AVATAR_IMAGE = COLUMN_DATA15;
    677         public static final int COLUMN_SYNC_DIRTY = COLUMN_SYNC1;
    678 
    679         public static final String SELECTION = Data.RAW_CONTACT_ID + "=?";
    680     }
    681 
    682     /**
    683      * Constants for a query to find a contact given a sample SyncAdapter user
    684      * ID.
    685      */
    686     final private static class ProfileQuery {
    687 
    688         private ProfileQuery() {
    689         }
    690 
    691         public final static String[] PROJECTION = new String[] {Data._ID};
    692 
    693         public final static int COLUMN_ID = 0;
    694 
    695         public static final String SELECTION =
    696             Data.MIMETYPE + "='" + SampleSyncAdapterColumns.MIME_PROFILE + "' AND "
    697                 + SampleSyncAdapterColumns.DATA_PID + "=?";
    698     }
    699 
    700     /**
    701      * Constants for a query to find a contact given a sample SyncAdapter user
    702      * ID.
    703      */
    704     final private static class UserIdQuery {
    705 
    706         private UserIdQuery() {
    707         }
    708 
    709         public final static String[] PROJECTION = new String[] {
    710             RawContacts._ID,
    711             RawContacts.CONTACT_ID
    712             };
    713 
    714         public final static int COLUMN_RAW_CONTACT_ID = 0;
    715         public final static int COLUMN_LINKED_CONTACT_ID = 1;
    716 
    717         public final static Uri CONTENT_URI = RawContacts.CONTENT_URI;
    718 
    719         public static final String SELECTION =
    720             RawContacts.ACCOUNT_TYPE + "='" + Constants.ACCOUNT_TYPE + "' AND "
    721                 + RawContacts.SOURCE_ID + "=?";
    722     }
    723 
    724     /**
    725      * Constants for a query to find SampleSyncAdapter contacts that are
    726      * in need of syncing to the server. This should cover new, edited,
    727      * and deleted contacts.
    728      */
    729     final private static class DirtyQuery {
    730 
    731         private DirtyQuery() {
    732         }
    733 
    734         public final static String[] PROJECTION = new String[] {
    735             RawContacts._ID,
    736             RawContacts.SOURCE_ID,
    737             RawContacts.DIRTY,
    738             RawContacts.DELETED,
    739             RawContacts.VERSION
    740             };
    741 
    742         public final static int COLUMN_RAW_CONTACT_ID = 0;
    743         public final static int COLUMN_SERVER_ID = 1;
    744         public final static int COLUMN_DIRTY = 2;
    745         public final static int COLUMN_DELETED = 3;
    746         public final static int COLUMN_VERSION = 4;
    747 
    748         public static final Uri CONTENT_URI = RawContacts.CONTENT_URI.buildUpon()
    749             .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
    750             .build();
    751 
    752         public static final String SELECTION =
    753             RawContacts.DIRTY + "=1 AND "
    754                 + RawContacts.ACCOUNT_TYPE + "='" + Constants.ACCOUNT_TYPE + "' AND "
    755                 + RawContacts.ACCOUNT_NAME + "=?";
    756     }
    757 
    758     /**
    759      * Constants for a query to get contact data for a given rawContactId
    760      */
    761     final private static class DataQuery {
    762 
    763         private DataQuery() {
    764         }
    765 
    766         public static final String[] PROJECTION =
    767             new String[] {Data._ID, RawContacts.SOURCE_ID, Data.MIMETYPE, Data.DATA1,
    768             Data.DATA2, Data.DATA3, Data.DATA15, Data.SYNC1};
    769 
    770         public static final int COLUMN_ID = 0;
    771         public static final int COLUMN_SERVER_ID = 1;
    772         public static final int COLUMN_MIMETYPE = 2;
    773         public static final int COLUMN_DATA1 = 3;
    774         public static final int COLUMN_DATA2 = 4;
    775         public static final int COLUMN_DATA3 = 5;
    776         public static final int COLUMN_DATA15 = 6;
    777         public static final int COLUMN_SYNC1 = 7;
    778 
    779         public static final Uri CONTENT_URI = Data.CONTENT_URI;
    780 
    781         public static final int COLUMN_PHONE_NUMBER = COLUMN_DATA1;
    782         public static final int COLUMN_PHONE_TYPE = COLUMN_DATA2;
    783         public static final int COLUMN_EMAIL_ADDRESS = COLUMN_DATA1;
    784         public static final int COLUMN_EMAIL_TYPE = COLUMN_DATA2;
    785         public static final int COLUMN_FULL_NAME = COLUMN_DATA1;
    786         public static final int COLUMN_GIVEN_NAME = COLUMN_DATA2;
    787         public static final int COLUMN_FAMILY_NAME = COLUMN_DATA3;
    788         public static final int COLUMN_AVATAR_IMAGE = COLUMN_DATA15;
    789         public static final int COLUMN_SYNC_DIRTY = COLUMN_SYNC1;
    790 
    791         public static final String SELECTION = Data.RAW_CONTACT_ID + "=?";
    792     }
    793 
    794     /**
    795      * Constants for a query to read basic contact columns
    796      */
    797     final public static class ContactQuery {
    798         private ContactQuery() {
    799         }
    800 
    801         public static final String[] PROJECTION =
    802             new String[] {Contacts._ID, Contacts.DISPLAY_NAME};
    803 
    804         public static final int COLUMN_ID = 0;
    805         public static final int COLUMN_DISPLAY_NAME = 1;
    806     }
    807 }
    808