/*
* DeliciousDroid - http://code.google.com/p/DeliciousDroid/
*
* Copyright (C) 2010 Matt Schmidt
*
* DeliciousDroid 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.
*
* DeliciousDroid 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 DeliciousDroid; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
package com.deliciousdroid.platform;
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.StreamItems;
import android.util.Log;
import com.deliciousdroid.R;
import com.deliciousdroid.Constants;
import com.deliciousdroid.client.DeliciousFeed;
import com.deliciousdroid.client.User;
import com.deliciousdroid.client.User.Status;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import org.apache.http.auth.AuthenticationException;
import org.json.JSONException;
/**
* Class for managing contacts sync related mOperations
*/
public class ContactManager {
/**
* Custom IM protocol used when storing status messages.
*/
public static final String CUSTOM_IM_PROTOCOL = "DeliciousDroid";
private static final String TAG = "ContactManager";
/**
* Synchronize raw contacts
*
* @param context The context of Authenticator Activity
* @param account The username for the account
* @param users The list of users
*/
public static synchronized void syncContacts(Context context,
String account, List<User> users) {
String userName;
long rawContactId = 0;
final ContentResolver resolver = context.getContentResolver();
List<Long> currentContacts = lookupAllContacts(resolver);
final BatchOperation batchOperation = new BatchOperation(context, resolver);
Log.d(TAG, "In SyncContacts");
for (final User user : users) {
userName = user.getUserName();
// Check to see if the contact needs to be inserted or updated
rawContactId = lookupRawContact(resolver, userName);
if (rawContactId == 0) {
// add new contact
Log.d(TAG, "In addContact");
addContact(context, account, user, batchOperation);
} else{
currentContacts.remove(rawContactId);
}
// A sync adapter should batch operations on multiple contacts,
// because it will make a dramatic performance difference.
if (batchOperation.size() >= 50) {
batchOperation.execute();
}
}
for(final Long l : currentContacts){
Log.d(TAG, "Deleting contact");
deleteContact(context, l, batchOperation);
}
batchOperation.execute();
}
/**
* Add a list of status messages to the contacts provider.
*
* @param context the context to use
* @param accountName the username of the logged in user
* @param statuses the list of statuses to store
*/
public static void insertStatuses(Context context, String username, List<User.Status> list) {
final ContentValues values = new ContentValues();
final ContentResolver resolver = context.getContentResolver();
final ArrayList<String> processedUsers = new ArrayList<String>();
final BatchOperation batchOperation = new BatchOperation(context, resolver);
for (final User.Status status : list) {
// Look up the user's sample SyncAdapter data row
final String userName = status.getUserName();
if(!processedUsers.contains(userName)){
final long profileId = lookupProfile(resolver, userName);
// Insert the activity into the stream
if (profileId > 0) {
values.put(StatusUpdates.DATA_ID, profileId);
values.put(StatusUpdates.STATUS, status.getStatus());
values.put(StatusUpdates.PROTOCOL, Im.PROTOCOL_CUSTOM);
values.put(StatusUpdates.CUSTOM_PROTOCOL, CUSTOM_IM_PROTOCOL);
values.put(StatusUpdates.IM_ACCOUNT, username);
values.put(StatusUpdates.IM_HANDLE, status.getUserName());
values.put(StatusUpdates.STATUS_TIMESTAMP, status.getTimeStamp().getTime());
values.put(StatusUpdates.STATUS_RES_PACKAGE, context.getPackageName());
values.put(StatusUpdates.STATUS_ICON, R.drawable.ic_main);
values.put(StatusUpdates.STATUS_LABEL, R.string.label);
batchOperation.add(ContactOperations.newInsertCpo(StatusUpdates.CONTENT_URI, true).withValues(values).build());
// A sync adapter should batch operations on multiple contacts,
// because it will make a dramatic performance difference.
if (batchOperation.size() >= 50) {
batchOperation.execute();
}
}
processedUsers.add(userName);
}
}
batchOperation.execute();
}
/**
* Add a list of status messages to the contacts provider.
*
* @param context the context to use
* @param accountName the username of the logged in user
* @param statuses the list of statuses to store
*/
@TargetApi(15)
public static void insertStreamStatuses(Context context, String username) {
final ContentValues values = new ContentValues();
final ContentResolver resolver = context.getContentResolver();
final BatchOperation batchOperation = new BatchOperation(context, resolver);
List<Long> currentContacts = lookupAllContacts(resolver);
for(long id : currentContacts){
String friendUsername = lookupUsername(resolver, id);
long watermark = lookupHighWatermark(resolver, id);
long newWatermark = watermark;
try {
List<Status> statuses = DeliciousFeed.fetchFriendStatuses(friendUsername);
for(Status status : statuses){
if(status.getTimeStamp().getTime() > watermark){
if(status.getTimeStamp().getTime() > newWatermark)
newWatermark = status.getTimeStamp().getTime();
values.clear();
values.put(StreamItems.RAW_CONTACT_ID, id);
values.put(StreamItems.TEXT, status.getStatus());
values.put(StreamItems.TIMESTAMP, status.getTimeStamp().getTime());
values.put(StreamItems.ACCOUNT_NAME, username);
values.put(StreamItems.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
values.put(StreamItems.RES_ICON, R.drawable.ic_main);
values.put(StreamItems.RES_PACKAGE, context.getPackageName());
values.put(StreamItems.RES_LABEL, R.string.label);
batchOperation.add(ContactOperations.newInsertCpo(StreamItems.CONTENT_URI, false).withValues(values).build());
// A sync adapter should batch operations on multiple contacts,
// because it will make a dramatic performance difference.
if (batchOperation.size() >= 50) {
batchOperation.execute();
}
}
}
values.clear();
values.put(RawContacts.SYNC1, Long.toString(newWatermark));
batchOperation.add(ContactOperations.newUpdateCpo(RawContacts.CONTENT_URI, false)
.withValues(values)
.withSelection(RawContacts._ID + "=?", new String[]{Long.toString(id)}).build());
batchOperation.execute();
} catch (AuthenticationException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Adds a single contact to the platform contacts provider.
*
* @param context the Authenticator Activity context
* @param accountName the account the contact belongs to
* @param user the sample SyncAdapter User object
*/
private static void addContact(Context context, String accountName,
User user, BatchOperation batchOperation) {
// Put the data in the contacts provider
final ContactOperations contactOp = ContactOperations.createNewContact(context, user.getUserName(), accountName, batchOperation);
contactOp.addName(user.getUserName()).addProfileAction(user.getUserName());
}
/**
* Deletes a contact from the platform contacts provider.
*
* @param context the Authenticator Activity context
* @param rawContactId the unique Id for this rawContact in contacts
* provider
*/
private static void deleteContact(Context context, long rawContactId, BatchOperation batchOperation) {
batchOperation.add(ContactOperations.newDeleteCpo(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), true).build());
}
/**
* Returns the RawContact id for a sample SyncAdapter contact, or 0 if the
* sample SyncAdapter user isn't found.
*
* @param context the Authenticator Activity context
* @param userId the sample SyncAdapter user ID to lookup
* @return the RawContact id, or 0 if not found
*/
private static long lookupRawContact(ContentResolver resolver, String userName) {
long authorId = 0;
final Cursor c =
resolver.query(RawContacts.CONTENT_URI, UserIdQuery.PROJECTION, UserIdQuery.SELECTION, new String[] {userName}, null);
try {
if (c.moveToFirst()) {
authorId = c.getLong(UserIdQuery.COLUMN_ID);
}
} finally {
if (c != null) {
c.close();
}
}
return authorId;
}
/**
* Returns the Data id for a sample SyncAdapter contact's profile row, or 0
* if the sample SyncAdapter user isn't found.
*
* @param resolver a content resolver
* @param userId the sample SyncAdapter user ID to lookup
* @return the profile Data row id, or 0 if not found
*/
private static long lookupProfile(ContentResolver resolver, String userName) {
long profileId = 0;
final Cursor c =
resolver.query(Data.CONTENT_URI, ProfileQuery.PROJECTION, ProfileQuery.SELECTION, new String[] {userName}, null);
try {
if (c != null && c.moveToFirst()) {
profileId = c.getLong(ProfileQuery.COLUMN_ID);
Log.d("ProfileLookup", Long.toString(c.getLong(ProfileQuery.COLUMN_ID)));
}
} finally {
if (c != null) {
c.close();
}
}
return profileId;
}
/**
* Returns the RawContact id for a sample SyncAdapter contact, or 0 if the
* sample SyncAdapter user isn't found.
*
* @param context the Authenticator Activity context
* @param userId the sample SyncAdapter user ID to lookup
* @return the RawContact id, or 0 if not found
*/
private static List<Long> lookupAllContacts(ContentResolver resolver) {
List<Long> result = new ArrayList<Long>();
final Cursor c =
resolver.query(RawContacts.CONTENT_URI, AllUsersQuery.PROJECTION, AllUsersQuery.SELECTION, null, null);
try {
while (c.moveToNext()) {
result.add(c.getLong(AllUsersQuery.COLUMN_ID));
}
} finally {
if (c != null) {
c.close();
}
}
return result;
}
private static long lookupHighWatermark(ContentResolver resolver, long id) {
long result = 0;
final Cursor c =
resolver.query(RawContacts.CONTENT_URI, HighWatermarkQuery.PROJECTION, HighWatermarkQuery.SELECTION, new String[] {Long.toString(id)}, null);
try {
while (c.moveToNext()) {
result = c.getLong(HighWatermarkQuery.COLUMN_ID);
}
} finally {
if (c != null) {
c.close();
}
}
return result;
}
private static String lookupUsername(ContentResolver resolver, long id) {
String result = null;
final Cursor c =
resolver.query(RawContacts.CONTENT_URI, UsernameQuery.PROJECTION, UsernameQuery.SELECTION, new String[] {Long.toString(id)}, null);
try {
while (c.moveToNext()) {
result = c.getString(UsernameQuery.COLUMN_ID);
}
} finally {
if (c != null) {
c.close();
}
}
return result;
}
/**
* Constants for a query to find a contact given a sample SyncAdapter user
* ID.
*/
private interface ProfileQuery {
public final static String[] PROJECTION = new String[] {Data._ID};
public final static int COLUMN_ID = 0;
public static final String SELECTION = Data.MIMETYPE + "='" + ContactSyncAdapterColumns.MIME_PROFILE + "' AND " + ContactSyncAdapterColumns.DATA_PID + "=?";
}
/**
* Constants for a query to find a contact given a sample SyncAdapter user
* ID.
*/
private interface UserIdQuery {
public final static String[] PROJECTION = new String[] {RawContacts._ID};
public final static int COLUMN_ID = 0;
public static final String SELECTION = RawContacts.ACCOUNT_TYPE + "='" + Constants.ACCOUNT_TYPE + "' AND " + RawContacts.SOURCE_ID + "=?";
}
/**
* Constants for a query to find all DeliciousDroid contacts
*/
private interface AllUsersQuery {
public final static String[] PROJECTION = new String[] {RawContacts._ID};
public final static int COLUMN_ID = 0;
public static final String SELECTION = RawContacts.ACCOUNT_TYPE + "='" + Constants.ACCOUNT_TYPE + "'";
}
private interface HighWatermarkQuery {
public final static String[] PROJECTION = new String[] {RawContacts.SYNC1};
public final static int COLUMN_ID = 0;
public static final String SELECTION = RawContacts._ID + "=?";
}
private interface UsernameQuery {
public final static String[] PROJECTION = new String[] {RawContacts.SOURCE_ID};
public final static int COLUMN_ID = 0;
public static final String SELECTION = RawContacts._ID + "=?";
}
}