Java tutorial
/* * Copyright (C) 2012 Danut Chereches * * Contact: Danut Chereches <admin@weednet.ro> * * This file is part of Facebook Contact Sync. * * Facebook Contact Sync is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Facebook Contact Sync. * If not, see <http://www.gnu.org/licenses/>. * */ package ro.weednet.contactssync.platform; import android.accounts.Account; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds; import android.provider.ContactsContract.CommonDataKinds.Photo; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.DisplayPhoto; import android.provider.ContactsContract.Groups; import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.StreamItems; import android.util.Log; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.http.auth.AuthenticationException; import org.json.JSONException; import ro.weednet.ContactsSync; import ro.weednet.contactssync.client.ContactPhoto; import ro.weednet.contactssync.client.NetworkUtilities; import ro.weednet.contactssync.client.RawContact; public class ContactManager { public static final String CUSTOM_IM_PROTOCOL = "fb"; private static final String TAG = "ContactManager"; public static final String GROUP_NAME = "Friends"; public static long ensureGroupExists(Context context, Account account) { final ContentResolver resolver = context.getContentResolver(); // Lookup the group long groupId = 0; final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] { Groups._ID }, Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=? AND " + Groups.TITLE + "=?", new String[] { account.name, account.type, GROUP_NAME }, null); if (cursor != null) { try { if (cursor.moveToFirst()) { groupId = cursor.getLong(0); } } finally { cursor.close(); } } if (groupId == 0) { // Group doesn't exist yet, so create it final ContentValues contentValues = new ContentValues(); contentValues.put(Groups.ACCOUNT_NAME, account.name); contentValues.put(Groups.ACCOUNT_TYPE, account.type); contentValues.put(Groups.TITLE, GROUP_NAME); // contentValues.put(Groups.GROUP_IS_READ_ONLY, true); contentValues.put(Groups.GROUP_VISIBLE, true); final Uri newGroupUri = resolver.insert(Groups.CONTENT_URI, contentValues); groupId = ContentUris.parseId(newGroupUri); } return groupId; } public static synchronized List<RawContact> updateContacts(Context context, Account account, List<RawContact> rawContacts, long groupId, boolean joinById, boolean allContacts) { ArrayList<RawContact> syncList = new ArrayList<RawContact>(); final ContentResolver resolver = context.getContentResolver(); final BatchOperation batchOperation = new BatchOperation(context, resolver); Log.d(TAG, "In updateContacts"); for (final RawContact rawContact : rawContacts) { final long rawContactId = lookupRawContact(resolver, account, rawContact.getUid()); if (rawContactId != 0) { updateContact(context, resolver, rawContact, true, true, rawContactId, batchOperation); syncList.add(rawContact); } else { long contactId = lookupContact(resolver, rawContact.getFullName(), rawContact.getUid(), joinById, allContacts); if (joinById && contactId > 0) { rawContact.setJoinContactId(contactId); } if (allContacts || contactId >= 0) { addContact(context, account, rawContact, groupId, true, batchOperation); syncList.add(rawContact); } } if (batchOperation.size() >= 10) { batchOperation.execute(); } } batchOperation.execute(); return syncList; } public static synchronized void updateContactDetails(Context context, List<RawContact> contacts, NetworkUtilities nu) throws AuthenticationException, IOException { final ContentResolver resolver = context.getContentResolver(); final BatchOperation batchOperation = new BatchOperation(context, resolver); final int pictureSize = getPhotoPickSize(context); Iterator<RawContact> iterator = contacts.iterator(); while (iterator.hasNext()) { RawContact contact = iterator.next(); try { Log.i(TAG, "checking user: " + contact.getUid()); //TODO: use selected value ContactPhoto photo = nu.getContactPhotoHD(contact, pictureSize, pictureSize); ContactManager.updateContactPhotoHd(context, resolver, contact.getRawContactId(), photo, batchOperation); } catch (JSONException e) { Log.e(TAG, e.toString()); } if (batchOperation.size() >= 10) { batchOperation.execute(); } } batchOperation.execute(); } public static List<RawContact> getLocalContacts(Context context, Uri uri) { Log.i(TAG, "*** Looking for local contacts"); List<RawContact> localContacts = new ArrayList<RawContact>(); final ContentResolver resolver = context.getContentResolver(); final Cursor c = resolver.query(uri, new String[] { Contacts._ID, RawContacts.SOURCE_ID }, null, null, null); try { while (c.moveToNext()) { final long rawContactId = c.getLong(0); final String serverContactId = c.getString(1); RawContact rawContact = RawContact.create(rawContactId, serverContactId); localContacts.add(rawContact); } } finally { if (c != null) { c.close(); } } Log.i(TAG, "*** ... found " + localContacts.size()); return localContacts; } public static int getLocalContactsCount(Context context, Account account) { Log.i(TAG, "*** Counting local contacts"); final Uri uri = RawContacts.CONTENT_URI.buildUpon() .appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name) .appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type).build(); final ContentResolver resolver = context.getContentResolver(); final Cursor c = resolver.query(uri, new String[] { Contacts._ID, RawContacts.SOURCE_ID }, null, null, null); int count = 0; try { count = c.getCount(); } catch (Exception e) { } finally { if (c != null) { c.close(); } } Log.i(TAG, "*** ... found " + count); return count; } public static List<RawContact> getStarredContacts(Context context, Uri uri) { Log.i(TAG, "*** Looking for starred contacts"); final ContentResolver resolver = context.getContentResolver(); Set<Long> contactIds = new HashSet<Long>(); Cursor c = resolver.query(RawContacts.CONTENT_URI, new String[] { RawContacts.CONTACT_ID }, RawContacts.STARRED + "!=0", null, null); try { while (c.moveToNext()) { contactIds.add(c.getLong(0)); } } finally { if (c != null) { c.close(); } } Log.i(TAG, "*** ... found " + contactIds.size() + " starred"); int i = 0; StringBuilder sb = new StringBuilder(); for (Long s : contactIds) { sb.append(s); if (++i >= Math.min(contactIds.size(), 50)) { break; } sb.append(","); } List<RawContact> contacts = new ArrayList<RawContact>(); c = resolver.query(uri, new String[] { Contacts._ID, RawContacts.SOURCE_ID }, RawContacts.CONTACT_ID + " IN (" + sb.toString() + ")", null, null); try { while (c.moveToNext()) { final long rawContactId = c.getLong(0); final String serverContactId = c.getString(1); RawContact rawContact = RawContact.create(rawContactId, serverContactId); contacts.add(rawContact); } } catch (Exception e) { Log.i(TAG, "failing .. " + e.toString()); } finally { if (c != null) { c.close(); } } Log.i(TAG, "*** ... and " + contacts.size() + " of mine " + sb.toString()); return contacts; } public static void addJoins(Context context, Account account, List<RawContact> rawContacts) { final ContentResolver resolver = context.getContentResolver(); final BatchOperation batchOperation = new BatchOperation(context, resolver); for (RawContact rawContact : rawContacts) { if (rawContact.getJoinContactId() > 0) { addAggregateException(context, account, rawContact, batchOperation); } } batchOperation.execute(); } public static void deleteContacts(Context context, List<RawContact> localContacts) { final ContentResolver resolver = context.getContentResolver(); final BatchOperation batchOperation = new BatchOperation(context, resolver); for (RawContact rawContact : localContacts) { final long rawContactId = rawContact.getRawContactId(); if (rawContactId > 0) { ContactManager.deleteContact(context, rawContactId, batchOperation); } } batchOperation.execute(); } public static void deleteMissingContacts(Context context, List<RawContact> localContacts, List<RawContact> serverContacts) { if (localContacts.size() == 0) { return; } final ContentResolver resolver = context.getContentResolver(); final BatchOperation batchOperation = new BatchOperation(context, resolver); final HashSet<String> contactsIds = new HashSet<String>(); for (RawContact rawContact : serverContacts) { contactsIds.add(rawContact.getUid()); } for (RawContact rawContact : localContacts) { if (!contactsIds.contains(rawContact.getUid())) { final long rawContactId = rawContact.getRawContactId(); if (rawContactId > 0) { ContactManager.deleteContact(context, rawContactId, batchOperation); } } } batchOperation.execute(); } public static void addContact(Context context, Account account, RawContact rawContact, long groupId, boolean inSync, BatchOperation batchOperation) { // Put the data in the contacts provider final ContactOperations contactOp = ContactOperations.createNewContact(context, rawContact.getUid(), account, inSync, batchOperation); contactOp.addName(rawContact.getFirstName(), rawContact.getLastName()).addGroupMembership(groupId) .addAvatar(rawContact.getAvatarUrl()); // If we have a serverId, then go ahead and create our status profile. // Otherwise skip it - and we'll create it after we sync-up to the // server later on. if (rawContact.getUid() != null) { contactOp.addProfileAction(rawContact.getUid()); } } public static void updateContact(Context context, ContentResolver resolver, RawContact rawContact, boolean updateAvatar, boolean inSync, long rawContactId, BatchOperation batchOperation) { ContactsSync app = ContactsSync.getInstance(); boolean existingAvatar = false; final Cursor c = resolver.query(DataQuery.CONTENT_URI, DataQuery.PROJECTION, DataQuery.SELECTION, new String[] { String.valueOf(rawContactId) }, null); final ContactOperations contactOp = ContactOperations.updateExistingContact(context, rawContactId, inSync, batchOperation); try { // Iterate over the existing rows of data, and update each one // with the information we received from the server. while (c.moveToNext()) { final long id = c.getLong(DataQuery.COLUMN_ID); final String mimeType = c.getString(DataQuery.COLUMN_MIMETYPE); final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, id); if (mimeType.equals(StructuredName.CONTENT_ITEM_TYPE)) { contactOp.updateName(uri, c.getString(DataQuery.COLUMN_GIVEN_NAME), c.getString(DataQuery.COLUMN_FAMILY_NAME), rawContact.getFirstName(), rawContact.getLastName()); /* } else if (mimeType.equals(Phone.CONTENT_ITEM_TYPE)) { final int type = c.getInt(DataQuery.COLUMN_PHONE_TYPE); if (type == Phone.TYPE_MOBILE) { existingCellPhone = true; contactOp.updatePhone( c.getString(DataQuery.COLUMN_PHONE_NUMBER), "5345345", uri); } else if (type == Phone.TYPE_HOME) { existingHomePhone = true; contactOp.updatePhone( c.getString(DataQuery.COLUMN_PHONE_NUMBER), "5345345", uri); } else if (type == Phone.TYPE_WORK) { existingWorkPhone = true; contactOp.updatePhone( c.getString(DataQuery.COLUMN_PHONE_NUMBER), "5345345", uri); } */ } else if (mimeType.equals(Photo.CONTENT_ITEM_TYPE)) { existingAvatar = true; if (app.getSyncType() == ContactsSync.SyncType.LEGACY) { contactOp.updateAvatar(c.getString(DataQuery.COLUMN_DATA1), rawContact.getAvatarUrl(), uri); } } } // while } finally { c.close(); } // Add the avatar if we didn't update the existing avatar if (app.getSyncType() != ContactsSync.SyncType.HARD && !existingAvatar) { contactOp.addAvatar(rawContact.getAvatarUrl()); } // If we don't have a status profile, then create one. This could // happen for contacts that were created on the client - we don't // create the status profile until after the first sync... final String serverId = rawContact.getUid(); final long profileId = lookupProfile(resolver, serverId); if (profileId <= 0) { contactOp.addProfileAction(serverId); } } public static void updateContactPhotoHd(Context context, ContentResolver resolver, long rawContactId, ContactPhoto photo, BatchOperation batchOperation) { final Cursor c = resolver.query(DataQuery.CONTENT_URI, DataQuery.PROJECTION, Data.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", new String[] { String.valueOf(rawContactId), Photo.CONTENT_ITEM_TYPE }, null); final ContactOperations contactOp = ContactOperations.updateExistingContact(context, rawContactId, true, batchOperation); if ((c != null) && c.moveToFirst()) { final long id = c.getLong(DataQuery.COLUMN_ID); final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, id); contactOp.updateAvatar(c.getString(DataQuery.COLUMN_DATA1), photo.getPhotoUrl(), uri); c.close(); } else { Log.i(TAG, "creating row, count: " + c.getCount()); contactOp.addAvatar(photo.getPhotoUrl()); } Log.d(TAG, "updating check timestamp"); final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); contactOp.updateSyncTimestamp1(System.currentTimeMillis(), uri); } public static void addAggregateException(Context context, Account account, RawContact rawContact, BatchOperation batchOperation) { final long rawContactId = lookupRawContact(context.getContentResolver(), account, rawContact.getUid()); if (rawContactId <= 0) { return; } ContentProviderOperation.Builder builder; builder = ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI) .withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1, Long.toString(rawContact.getJoinContactId())) .withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, Long.toString(rawContactId)) .withValue(ContactsContract.AggregationExceptions.TYPE, ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER); batchOperation.add(builder.build()); } private static void deleteContact(Context context, long rawContactId, BatchOperation batchOperation) { batchOperation.add(ContactOperations .newDeleteCpo(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), true, true) .build()); } private static long lookupRawContact(ContentResolver resolver, Account account, String serverContactId) { long rawContactId = 0; final Cursor c = resolver.query(UserIdQuery.CONTENT_URI, UserIdQuery.PROJECTION, UserIdQuery.SELECTION, new String[] { account.name, account.type, serverContactId }, null); try { if ((c != null) && c.moveToFirst()) { rawContactId = c.getLong(UserIdQuery.COLUMN_RAW_CONTACT_ID); } } finally { if (c != null) { c.close(); } } return rawContactId; } private static long lookupContact(ContentResolver resolver, String name, String fb_id, boolean joinById, boolean syncAll) { Cursor c; if (joinById) { c = resolver.query(ContactsContract.Data.CONTENT_URI, //table new String[] { ContactsContract.Data.RAW_CONTACT_ID }, //select (projection) ContactsContract.Data.MIMETYPE + "=? AND " + CommonDataKinds.Note.NOTE + " LIKE ?", //where new String[] { ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE, "%" + fb_id + "%" }, //params null //sort ); try { if (c != null && c.moveToFirst()) { return c.getLong(0); } } finally { if (c != null) { c.close(); } } } if (syncAll) { return -1; } c = resolver.query(Contacts.CONTENT_URI, //table new String[] { Contacts._ID }, //select (projection) Contacts.DISPLAY_NAME + "=?", //where new String[] { name }, //params null //sort ); try { if (c != null && c.getCount() > 0) { return 0; } } finally { if (c != null) { c.close(); } } return -1; } private static long lookupProfile(ContentResolver resolver, String userId) { long profileId = 0; final Cursor c = resolver.query(Data.CONTENT_URI, ProfileQuery.PROJECTION, ProfileQuery.SELECTION, new String[] { userId }, null); try { if ((c != null) && c.moveToFirst()) { profileId = c.getLong(ProfileQuery.COLUMN_ID); } } finally { if (c != null) { c.close(); } } return profileId; } public static int getPhotoPickSize(Context context) { Cursor c = context.getContentResolver().query(DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI, new String[] { DisplayPhoto.DISPLAY_MAX_DIM }, null, null, null); try { c.moveToFirst(); return c.getInt(0); } catch (Exception e) { } finally { if (c != null) { c.close(); } } return ContactsSync.getInstance().getPictureSize(); } public static int getStreamItemLimit(Context context) { Cursor c = context.getContentResolver().query(StreamItems.CONTENT_LIMIT_URI, new String[] { StreamItems.MAX_ITEMS }, null, null, null); try { c.moveToFirst(); return c.getInt(0); } catch (Exception e) { } finally { if (c != null) { c.close(); } } return 1; } final public static class EditorQuery { private EditorQuery() { } public static final String[] PROJECTION = new String[] { RawContacts.ACCOUNT_NAME, Data._ID, RawContacts.Entity.DATA_ID, Data.MIMETYPE, Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA15, Data.SYNC1 }; public static final int COLUMN_ACCOUNT_NAME = 0; public static final int COLUMN_RAW_CONTACT_ID = 1; public static final int COLUMN_DATA_ID = 2; public static final int COLUMN_MIMETYPE = 3; public static final int COLUMN_DATA1 = 4; public static final int COLUMN_DATA2 = 5; public static final int COLUMN_DATA3 = 6; public static final int COLUMN_DATA15 = 7; public static final int COLUMN_SYNC1 = 8; public static final int COLUMN_PHONE_NUMBER = COLUMN_DATA1; public static final int COLUMN_PHONE_TYPE = COLUMN_DATA2; public static final int COLUMN_EMAIL_ADDRESS = COLUMN_DATA1; public static final int COLUMN_EMAIL_TYPE = COLUMN_DATA2; public static final int COLUMN_GIVEN_NAME = COLUMN_DATA1; public static final int COLUMN_FAMILY_NAME = COLUMN_DATA2; public static final int COLUMN_BIRTHDAY_DATE = COLUMN_DATA1; public static final int COLUMN_AVATAR_IMAGE = COLUMN_DATA15; public static final int COLUMN_SYNC_DIRTY = COLUMN_SYNC1; public static final String SELECTION = Data.RAW_CONTACT_ID + "=?"; } final private static class ProfileQuery { private ProfileQuery() { } public final static String[] PROJECTION = new String[] { Data._ID }; public final static int COLUMN_ID = 0; public static final String SELECTION = Data.MIMETYPE + "='" + SyncAdapterColumns.MIME_PROFILE + "' AND " + SyncAdapterColumns.DATA_PID + "=?"; } final private static class UserIdQuery { private UserIdQuery() { } public final static String[] PROJECTION = new String[] { RawContacts._ID, RawContacts.CONTACT_ID }; public final static int COLUMN_RAW_CONTACT_ID = 0; // public final static int COLUMN_LINKED_CONTACT_ID = 1; public final static Uri CONTENT_URI = RawContacts.CONTENT_URI; public static final String SELECTION = RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=? AND " + RawContacts.SOURCE_ID + "=?"; } @SuppressWarnings("unused") final private static class DataQuery { private DataQuery() { } public static final String[] PROJECTION = new String[] { Data._ID, RawContacts.SOURCE_ID, Data.MIMETYPE, Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA15, Data.SYNC1 }; public static final int COLUMN_ID = 0; public static final int COLUMN_SERVER_ID = 1; public static final int COLUMN_MIMETYPE = 2; public static final int COLUMN_DATA1 = 3; public static final int COLUMN_DATA2 = 4; public static final int COLUMN_DATA3 = 5; public static final int COLUMN_DATA15 = 6; public static final int COLUMN_SYNC1 = 7; public static final Uri CONTENT_URI = Data.CONTENT_URI; public static final int COLUMN_PHONE_NUMBER = COLUMN_DATA1; public static final int COLUMN_PHONE_TYPE = COLUMN_DATA2; public static final int COLUMN_EMAIL_ADDRESS = COLUMN_DATA1; public static final int COLUMN_EMAIL_TYPE = COLUMN_DATA2; public static final int COLUMN_BIRTHDAY_DATE = COLUMN_DATA1; public static final int COLUMN_BIRTHDAY_TYPE = COLUMN_DATA2; public static final int COLUMN_GIVEN_NAME = COLUMN_DATA1; public static final int COLUMN_FAMILY_NAME = COLUMN_DATA2; public static final int COLUMN_AVATAR_IMAGE = COLUMN_DATA15; public static final int COLUMN_SYNC_TIMESTAMP = COLUMN_SYNC1; public static final int COLUMN_SYNC_PHOTO_TIMESTAMP = COLUMN_SYNC1; public static final String SELECTION = Data.RAW_CONTACT_ID + "=?"; } final public static class ContactQuery { private ContactQuery() { } public static final String[] PROJECTION = new String[] { Contacts._ID, Contacts.DISPLAY_NAME }; public static final int COLUMN_ID = 0; public static final int COLUMN_DISPLAY_NAME = 1; } }