org.codarama.haxsync.services.ContactsSyncAdapterService.java Source code

Java tutorial

Introduction

Here is the source code for org.codarama.haxsync.services.ContactsSyncAdapterService.java

Source

/*
 * Copyright (c) 2016 Codarama.org, All Rights Reserved
 *
 * Codarama HaxSync 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 2
 * of the License, or (at your option) any later version.
 *
 * Codarama HaxSync 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package org.codarama.haxsync.services;

import android.accounts.Account;
import android.accounts.OperationCanceledException;
import android.app.Service;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.content.SyncResult;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContacts.Entity;
import android.util.Log;

import com.jjnford.android.util.Shell.ShellException;

import org.apache.commons.lang3.StringUtils;
import org.codarama.haxsync.R;
import org.codarama.haxsync.provider.facebook.FacebookGraphFriend;
import org.codarama.haxsync.provider.facebook.Status;
import org.codarama.haxsync.utilities.BitmapUtil;
import org.codarama.haxsync.utilities.ContactUtil;
import org.codarama.haxsync.utilities.ContactUtil.Photo;
import org.codarama.haxsync.utilities.DeviceUtil;
import org.codarama.haxsync.utilities.FacebookUtil;
import org.codarama.haxsync.utilities.RootUtil;
import org.codarama.haxsync.utilities.WebUtil;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class is scheduled for immanent destruction due, amongst other things : it being large, not
 * conforming to the single responsibility principle, etc.
 *
 * Ideally it's functions would be split into several different classes:
 * - SyncAdapter for contact photos
 * - SyncAdapter for facebook contacts (if such is deemed possible to write)
 * - HTCDataManager for managing HTCData
 * - SyncContacts for managing imported facebook/other contacts
 * - etc
 */
@Deprecated
public class ContactsSyncAdapterService extends Service {
    private static final String TAG = "LegacyAdapterService";
    public static ContentResolver mContentResolver = null;
    private static SyncAdapterImpl sSyncAdapter = null;
    private static String UsernameColumn = ContactsContract.RawContacts.SYNC1;
    private static String PhotoTimestampColumn = ContactsContract.RawContacts.SYNC2;

    public ContactsSyncAdapterService() {
        super();
    }

    private static String matches(Set<String> phoneContacts, String fbContact, int maxdistance) {
        if (maxdistance == 0) {
            if (phoneContacts.contains(fbContact)) {
                return fbContact;
            }
            return null;
            //return phoneContacts.contains(fbContact);
        }
        int bestDistance = maxdistance;
        String bestMatch = null;
        for (String contact : phoneContacts) {
            int distance = StringUtils.getLevenshteinDistance(contact != null ? contact.toLowerCase() : "",
                    fbContact != null ? fbContact.toLowerCase() : "");
            if (distance <= bestDistance) {
                //Log.i("FOUND MATCH", "Phone Contact: " + contact +" FB Contact: " + fbContact +" distance: " + distance + "max distance: " +maxdistance);
                bestMatch = contact;
                bestDistance = distance;
            }
        }
        return bestMatch;
    }

    private static void addContact(Account account, String name, String username) {
        ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();

        ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
        builder.withValue(RawContacts.ACCOUNT_NAME, account.name);
        builder.withValue(RawContacts.ACCOUNT_TYPE, account.type);
        builder.withValue(RawContacts.SYNC1, username);
        operationList.add(builder.build());

        builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
        builder.withValueBackReference(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID, 0);
        builder.withValue(ContactsContract.Data.MIMETYPE,
                ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
        builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
        operationList.add(builder.build());

        builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
        builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0);
        builder.withValue(ContactsContract.Data.MIMETYPE,
                "vnd.android.cursor.item/vnd.org.codarama.haxsync.profile");
        builder.withValue(ContactsContract.Data.DATA1, username);
        builder.withValue(ContactsContract.Data.DATA2, "Facebook Profile");
        builder.withValue(ContactsContract.Data.DATA3, "View profile");
        operationList.add(builder.build());

        try {
            mContentResolver.applyBatch(ContactsContract.AUTHORITY, operationList);
        } catch (Exception e) {
            Log.e("Error", e.getLocalizedMessage());
        }
    }

    private static void addSelfContact(Account account, int maxSize, boolean square, boolean faceDetect,
            boolean force, boolean root, int rootsize, File cacheDir, boolean google) {

        Uri rawContactUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI.buildUpon()
                .appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
                .appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type).build();

        long ID = -2;
        String username;
        String email;
        FacebookGraphFriend user = FacebookUtil.getSelfInfo();
        if (user == null)
            return;
        Cursor cursor = mContentResolver.query(rawContactUri, new String[] { BaseColumns._ID, UsernameColumn },
                null, null, null);
        if (cursor.getCount() > 0) {
            cursor.moveToFirst();
            ID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
            username = cursor.getString(cursor.getColumnIndex(UsernameColumn));
            cursor.close();
        } else {
            cursor.close();
            username = user.getUserName();
            email = user.getEmail();

            ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();

            ContentProviderOperation.Builder builder = ContentProviderOperation
                    .newInsert(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI);
            builder.withValue(RawContacts.ACCOUNT_NAME, account.name);
            builder.withValue(RawContacts.ACCOUNT_TYPE, account.type);
            builder.withValue(RawContacts.SYNC1, username);
            operationList.add(builder.build());

            builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
            builder.withValueBackReference(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID, 0);
            builder.withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
            builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, account.name);
            operationList.add(builder.build());

            builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
            builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0);
            builder.withValue(ContactsContract.Data.MIMETYPE,
                    "vnd.android.cursor.item/vnd.org.codarama.haxsync.profile");
            builder.withValue(ContactsContract.Data.DATA1, username);
            builder.withValue(ContactsContract.Data.DATA2, "Facebook Profile");
            builder.withValue(ContactsContract.Data.DATA3, "View profile");
            operationList.add(builder.build());

            if (email != null) {
                builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
                builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0);
                builder.withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
                builder.withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email);
                operationList.add(builder.build());
            }

            try {
                mContentResolver.applyBatch(ContactsContract.AUTHORITY, operationList);
            } catch (Exception e) {
                // FIXME catching generic Exception class is not the best thing to do
                Log.e("Error", e.getLocalizedMessage());
                return;
            }
            cursor = mContentResolver.query(rawContactUri, new String[] { BaseColumns._ID }, null, null, null);
            if (cursor.getCount() > 0) {
                cursor.moveToFirst();
                ID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
                cursor.close();
            } else {
                Log.i(TAG, "NO SELF CONTACT FOUND");
                return;
            }
        }
        Log.i("self contact", "id: " + ID + " uid: " + username);
        if (ID != -2 && username != null) {

            updateContactPhoto(ID, 0, maxSize, square, user.getPicURL(), faceDetect, true, root, rootsize, cacheDir,
                    google, true);

        }
    }

    private static void updateContactStatus(long rawContactId, String status, long timeStamp) {
        if (status != null && timeStamp != 0) {
            ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
            Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
            Uri entityUri = Uri.withAppendedPath(rawContactUri, Entity.CONTENT_DIRECTORY);
            Cursor c = mContentResolver.query(entityUri,
                    new String[] { RawContacts.SOURCE_ID, Entity.DATA_ID, Entity.MIMETYPE, Entity.DATA1 }, null,
                    null, null);
            try {
                while (c.moveToNext()) {
                    if (!c.isNull(1)) {
                        String mimeType = c.getString(2);

                        if (mimeType.equals("vnd.android.cursor.item/vnd.org.codarama.haxsync.profile")) {
                            ContentProviderOperation.Builder builder = ContentProviderOperation
                                    .newInsert(ContactsContract.StatusUpdates.CONTENT_URI);
                            builder.withValue(ContactsContract.StatusUpdates.DATA_ID, c.getLong(1));
                            builder.withValue(ContactsContract.StatusUpdates.STATUS, status);
                            builder.withValue(ContactsContract.StatusUpdates.STATUS_RES_PACKAGE,
                                    "org.codarama.haxsync");
                            builder.withValue(ContactsContract.StatusUpdates.STATUS_LABEL, R.string.app_name);
                            builder.withValue(ContactsContract.StatusUpdates.STATUS_ICON, R.drawable.icon);
                            builder.withValue(ContactsContract.StatusUpdates.STATUS_TIMESTAMP, timeStamp);
                            operationList.add(builder.build());
                        }
                    }
                }
            } finally {
                c.close();
            }
            try {
                mContentResolver.applyBatch(ContactsContract.AUTHORITY, operationList);
            } catch (RemoteException e) {
                Log.e("Error", e.getLocalizedMessage());
            } catch (OperationApplicationException e) {
                Log.e("Error", e.getLocalizedMessage());
            }
        }
    }

    private static void updateContactPhoto(long rawContactId, long timestamp, int maxSize, boolean square,
            String imgUrl, boolean faceDetect, boolean force, boolean root, int rootsize, File cacheDir,
            boolean google, boolean primary) {
        if (imgUrl != null) {

            String where = ContactsContract.Data.RAW_CONTACT_ID + " = '" + rawContactId + "' AND "
                    + ContactsContract.Data.MIMETYPE + " = '"
                    + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "'";

            //getting the old timestamp
            String oldurl = "";
            boolean newpic = force;

            if (!newpic) {
                Cursor c1 = mContentResolver.query(ContactsContract.Data.CONTENT_URI,
                        new String[] { ContactsContract.Data.SYNC3 }, where, null, null);
                if (c1.getCount() > 0) {
                    c1.moveToLast();

                    if (!c1.isNull(c1.getColumnIndex(ContactsContract.Data.SYNC3))) {
                        oldurl = c1.getString(c1.getColumnIndex(ContactsContract.Data.SYNC3));
                        //Log.i(TAG, "read old timestamp: " + oldTimestamp);
                    }
                }
                c1.close();

                //Log.i(TAG, "Old Timestamp " +String.valueOf(oldTimestamp) + "new timestamp: " + String.valueOf(timestamp));

                if (!oldurl.equals(imgUrl)) {
                    Log.i(TAG, "OLD URL: " + oldurl);
                    Log.i(TAG, "NEW URL: " + imgUrl);
                    newpic = true;
                }

            }

            if (newpic) {
                Log.i(TAG, "getting new image, " + imgUrl);
                //   Log.i(TAG, "Old Timestamp " +String.valueOf(oldTimestamp) + "new timestamp: " + String.valueOf(timestamp));

                byte[] photo = WebUtil.download(imgUrl);
                byte[] origPhoto = photo;

                /*if(square)
                       photo = BitmapUtil.resize(photo, maxSize, faceDetect);*/

                ContactUtil.Photo photoi = new Photo();
                photoi.data = photo;
                photoi.timestamp = timestamp;
                photoi.url = imgUrl;

                ContactUtil.updateContactPhoto(mContentResolver, rawContactId, photoi, primary);

                if (root) {
                    Cursor c1 = mContentResolver.query(ContactsContract.Data.CONTENT_URI,
                            new String[] { ContactsContract.CommonDataKinds.Photo.PHOTO_FILE_ID }, where, null,
                            null);
                    if (c1.getCount() > 0) {
                        c1.moveToLast();
                        String photoID = c1
                                .getString(c1.getColumnIndex(ContactsContract.CommonDataKinds.Photo.PHOTO_FILE_ID));
                        c1.close();
                        if (photoID != null) {
                            photo = BitmapUtil.resize(origPhoto, rootsize, faceDetect);
                            String picpath = DeviceUtil.saveBytes(photo, cacheDir);
                            try {
                                String newpath = RootUtil.movePic(picpath, photoID);
                                RootUtil.changeOwner(newpath);
                            } catch (Exception e) {
                                Log.e("ROOT EXCEPTION", e.getMessage());
                                // TODO: handle exception
                            }
                        }
                    }

                }
                Log.i("google photo sync", String.valueOf(google));
                if (google) {
                    for (long raw : ContactUtil.getRawContacts(mContentResolver, rawContactId, "com.google")) {
                        Log.i("google rawid", String.valueOf(raw));
                        ContactUtil.updateContactPhoto(mContentResolver, raw, photoi, false);
                    }
                }
            }
        }
    }

    private static HashMap<String, Long> loadHTCData(Context c) {
        mContentResolver = c.getContentResolver();
        /*ArrayList<Long> contactIDs = new ArrayList<Long>();
        Cursor c1 = mContentResolver.query(ContactsContract.Contacts.CONTENT_URI, new String[] { BaseColumns._ID }, ContactsContract.Contacts.IN_VISIBLE_GROUP +" = 1", null, null);
        while (c1.moveToNext()){
           contactIDs.add(c1.getLong(c1.getColumnIndex(BaseColumns._ID)));
        }
        c1.close();*/

        HashMap<String, Long> contacts = new HashMap<String, Long>();
        //Cursor cursor = mContentResolver.query(ContactsContract.Data.CONTENT_URI, null, ContactsContract.Data.MIMETYPE +"= ?", ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE, null);
        String noteWhere = ContactsContract.Data.MIMETYPE + " = ?";
        String[] noteWhereParams = new String[] { ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE };
        Cursor cursor = mContentResolver.query(ContactsContract.Data.CONTENT_URI,
                new String[] { ContactsContract.Data.RAW_CONTACT_ID, ContactsContract.CommonDataKinds.Note.NOTE },
                noteWhere, noteWhereParams, null);
        while (cursor.moveToNext()) {
            try {
                String note = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Note.NOTE));
                if (note != null) {
                    if (note.startsWith("<HTCData>")) {
                        Pattern fbPattern = Pattern.compile("<Facebook>id:(.*)/friendof.*</Facebook>",
                                Pattern.CASE_INSENSITIVE);
                        Matcher fbMatcher = fbPattern.matcher(note);

                        while (fbMatcher.find()) {
                            String uid = fbMatcher.group(1);
                            //Log.i("found HTCDATA", uid);
                            Long rawID = cursor
                                    .getLong(cursor.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID));
                            contacts.put(uid, rawID);
                        }

                        //String uid = note.split("/friendof")[0].substring(22);

                    }
                }
            } catch (IllegalStateException e) {
                Log.e(TAG, "Error loading HTCDATA");
                break;
            }
        }
        cursor.close();
        return contacts;
    }

    private static HashMap<String, Long> loadPhoneContacts(Context c) {
        mContentResolver = c.getContentResolver();
        HashMap<String, Long> contacts = new HashMap<String, Long>();
        Cursor cursor = mContentResolver.query(Phone.CONTENT_URI,
                new String[] { Phone.DISPLAY_NAME, Phone.RAW_CONTACT_ID }, null, null, null);
        while (cursor.moveToNext()) {
            contacts.put(cursor.getString(cursor.getColumnIndex(Phone.DISPLAY_NAME)),
                    cursor.getLong(cursor.getColumnIndex(Phone.RAW_CONTACT_ID)));
            //names.add(cursor.getString(cursor.getColumnIndex(Phone.DISPLAY_NAME)));
        }
        cursor.close();
        return contacts;
    }

    public static HashMap<String, SyncEntry> getLocalContacts(Account account) {
        Uri rawContactUri = RawContacts.CONTENT_URI.buildUpon()
                .appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
                .appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type).build();
        HashMap<String, SyncEntry> localContacts = new HashMap<String, SyncEntry>();
        Cursor c1 = mContentResolver.query(rawContactUri,
                new String[] { BaseColumns._ID, UsernameColumn, PhotoTimestampColumn }, null, null, null);
        while (c1.moveToNext()) {
            SyncEntry entry = new SyncEntry();
            entry.raw_id = c1.getLong(c1.getColumnIndex(BaseColumns._ID));
            localContacts.put(c1.getString(1), entry);
        }
        c1.close();
        return localContacts;
    }

    @SuppressWarnings("unused")
    private static void performSync(Context context, Account account, Bundle extras, String authority,
            ContentProviderClient provider, SyncResult syncResult) throws OperationCanceledException {
        SharedPreferences prefs = context.getSharedPreferences(context.getPackageName() + "_preferences",
                MODE_MULTI_PROCESS);

        mContentResolver = context.getContentResolver();

        FacebookUtil.refreshPermissions(context);

        //TODO: Clean up stuff that isn't needed anymore since Graph API
        boolean cropPhotos = true;
        boolean sync = prefs.getBoolean("sync_status", true);
        boolean syncNew = prefs.getBoolean("status_new", true);
        boolean syncLocation = prefs.getBoolean("sync_location", true);
        boolean syncSelf = prefs.getBoolean("sync_self", false);
        boolean imageDefault = prefs.getBoolean("image_primary", true);

        boolean oldStatus = sync && (!syncNew || (Build.VERSION.SDK_INT < 15));
        boolean faceDetect = true;

        boolean root = prefs.getBoolean("root_enabled", false);
        int rootSize = 512;

        if (FacebookUtil.authorize(context, account)) {

            HashMap<String, SyncEntry> localContacts = getLocalContacts(account);
            HashMap<String, Long> names = loadPhoneContacts(context);
            HashMap<String, Long> uids = loadHTCData(context);
            //Log.i("CONTACTS", names.toString());
            boolean phoneOnly = prefs.getBoolean("phone_only", true);
            /*if (phoneOnly){
               names = loadPhoneContacts(context);
            }*/
            boolean wifiOnly = prefs.getBoolean("wifi_only", false);
            boolean syncEmail = prefs.getBoolean("sync_facebook_email", false);
            boolean syncBirthday = prefs.getBoolean("sync_contact_birthday", true);
            boolean force = prefs.getBoolean("force_dl", false);
            boolean google = prefs.getBoolean("update_google_photos", false);
            boolean ignoreMiddleaNames = prefs.getBoolean("ignore_middle_names", false);
            boolean addMeToFriends = prefs.getBoolean("add_me_to_friends", false);
            Log.i("google", String.valueOf(google));
            int fuzziness = Integer.parseInt(prefs.getString("fuzziness", "2"));
            Set<String> addFriends = prefs.getStringSet("add_friends", new HashSet<String>());
            Log.i(TAG, "phone_only: " + Boolean.toString(phoneOnly));
            Log.i(TAG, "wifi_only: " + Boolean.toString(wifiOnly));
            Log.i(TAG, "is wifi: " + Boolean.toString(DeviceUtil.isWifi(context)));
            Log.i(TAG, "phone contacts: " + names.toString());
            Log.i(TAG, "using old status api: " + String.valueOf(oldStatus));
            Log.i(TAG, "ignoring middle names : " + String.valueOf(ignoreMiddleaNames));
            Log.i(TAG, "add me to friends : " + String.valueOf(addMeToFriends));
            boolean chargingOnly = prefs.getBoolean("charging_only", false);
            int maxsize = BitmapUtil.getMaxSize(context.getContentResolver());
            File cacheDir = context.getCacheDir();
            Log.i("CACHE DIR", cacheDir.getAbsolutePath());
            Log.i("MAX IMAGE SIZE", String.valueOf(maxsize));
            if (!((wifiOnly && !DeviceUtil.isWifi(context)) || (chargingOnly && !DeviceUtil.isCharging(context)))) {
                try {
                    if (syncSelf) {
                        addSelfContact(account, maxsize, cropPhotos, faceDetect, force, root, rootSize, cacheDir,
                                google);
                    }
                    List<FacebookGraphFriend> friends = FacebookUtil.getFriends(maxsize, addMeToFriends);
                    for (FacebookGraphFriend friend : friends) {
                        String uid = friend.getUserName();
                        String friendName = friend.getName(ignoreMiddleaNames);
                        if (friendName != null && uid != null) {
                            String match = matches(names.keySet(), friendName, fuzziness);

                            if (!(phoneOnly && (match == null) && !uids.containsKey(uid))
                                    || addFriends.contains(friendName)) {
                                // STEP 1. Add contact - if the contact is not part of the HTCData records and does not match any
                                // of the fuzziness names we add them to the list of contacts with the intention to make them available
                                // for manual merge (I guess)
                                if (localContacts.get(uid) == null) {
                                    //String name = friend.getString("name");
                                    //Log.i(TAG, name + " already on phone: " + Boolean.toString(names.contains(name)));

                                    addContact(account, friendName, uid);

                                    SyncEntry entry = new SyncEntry();
                                    Uri rawContactUr = RawContacts.CONTENT_URI.buildUpon()
                                            .appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
                                            .appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type)
                                            .appendQueryParameter(RawContacts.Data.DATA1, uid).build();
                                    Cursor c = mContentResolver.query(rawContactUr,
                                            new String[] { BaseColumns._ID }, null, null, null);
                                    c.moveToLast();
                                    long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
                                    c.close();
                                    //   Log.i("ID", Long.toString(id));
                                    entry.raw_id = id;
                                    localContacts.put(uid, entry);
                                    if (uids.containsKey(uid)) {
                                        ContactUtil.merge(context, uids.get(uid), id);
                                    } else if (names.containsKey(match)) {
                                        ContactUtil.merge(context, names.get(match), id);
                                    }
                                    //localContacts = loadContacts(accounts, context);
                                }

                                // STEP 2. Set contact photo

                                SyncEntry contact = localContacts.get(uid);

                                updateContactPhoto(contact.raw_id, friend.getPicTimestamp(), maxsize, cropPhotos,
                                        friend.getPicURL(), faceDetect, force, root, rootSize, cacheDir, google,
                                        imageDefault);

                                if (syncEmail && !FacebookUtil.RESPECT_FACEBOOK_POLICY)
                                    ContactUtil.addEmail(context, contact.raw_id, friend.getEmail());

                                if (syncLocation && !FacebookUtil.RESPECT_FACEBOOK_POLICY) {
                                    ContactUtil.updateContactLocation(contact.raw_id, friend.getLocation());
                                }

                                if (oldStatus && !FacebookUtil.RESPECT_FACEBOOK_POLICY) {
                                    ArrayList<Status> statuses = friend.getStatuses();
                                    if (statuses.size() >= 1) {
                                        updateContactStatus(contact.raw_id, statuses.get(0).getMessage(),
                                                statuses.get(0).getTimestamp());
                                    }

                                }
                                if (syncBirthday && !FacebookUtil.RESPECT_FACEBOOK_POLICY) {
                                    String birthday = friend.getBirthday();

                                    if (birthday != null) {
                                        ContactUtil.addBirthday(contact.raw_id, birthday);
                                    }
                                }

                            }
                        }
                    }
                } catch (Exception e) {
                    // FIXME catching the generic Exception class is not hte best thing to do
                    Log.e("ERROR", e.toString());
                }

                if (root) {
                    try {
                        RootUtil.refreshContacts();
                    } catch (ShellException e) {
                        Log.e("Error", e.getLocalizedMessage());
                    }
                }
                if (force) {
                    SharedPreferences.Editor editor = prefs.edit();
                    editor.putBoolean("force_dl", false);
                    editor.commit();

                }

            } else {
                SharedPreferences.Editor editor = prefs.edit();
                editor.putBoolean("missed_contact_sync", true);
                editor.commit();
            }
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        IBinder ret = getSyncAdapter().getSyncAdapterBinder();
        return ret;
    }

    private SyncAdapterImpl getSyncAdapter() {
        if (sSyncAdapter == null)
            sSyncAdapter = new SyncAdapterImpl(this);
        return sSyncAdapter;
    }

    private static class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
        private Context mContext;

        public SyncAdapterImpl(Context context) {
            super(context, true);
            mContext = context;
        }

        @Override
        public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
                SyncResult syncResult) {
            try {
                ContactsSyncAdapterService.performSync(mContext, account, extras, authority, provider, syncResult);
            } catch (OperationCanceledException e) {
                Log.e("Error", e.getLocalizedMessage());
            }
        }
    }

    public static class SyncEntry {
        public Long raw_id = 0L;
    }
}