Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.providers.contacts;
     18 
     19 import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
     20 import com.android.providers.contacts.ContactsDatabaseHelper.PhoneColumns;
     21 import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
     22 import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
     23 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
     24 
     25 import android.content.ContentResolver;
     26 import android.content.ContentValues;
     27 import android.content.Context;
     28 import android.database.Cursor;
     29 import android.database.DatabaseUtils;
     30 import android.database.sqlite.SQLiteDatabase;
     31 import android.database.sqlite.SQLiteException;
     32 import android.database.sqlite.SQLiteStatement;
     33 import android.provider.CallLog.Calls;
     34 import android.provider.ContactsContract.Contacts;
     35 import android.provider.ContactsContract.Data;
     36 import android.provider.ContactsContract.Groups;
     37 import android.provider.ContactsContract.RawContacts;
     38 import android.provider.ContactsContract.CommonDataKinds.Email;
     39 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
     40 import android.provider.ContactsContract.CommonDataKinds.Im;
     41 import android.provider.ContactsContract.CommonDataKinds.Note;
     42 import android.provider.ContactsContract.CommonDataKinds.Organization;
     43 import android.provider.ContactsContract.CommonDataKinds.Phone;
     44 import android.provider.ContactsContract.CommonDataKinds.Photo;
     45 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
     46 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
     47 import android.telephony.PhoneNumberUtils;
     48 import android.text.TextUtils;
     49 import android.util.Log;
     50 
     51 import java.io.File;
     52 
     53 public class LegacyContactImporter {
     54 
     55     public static final String TAG = "LegacyContactImporter";
     56 
     57     private static final int MAX_ATTEMPTS = 5;
     58     private static final int DELAY_BETWEEN_ATTEMPTS = 2000;
     59 
     60     public static final String DEFAULT_ACCOUNT_TYPE = "com.google";
     61     private static final String DATABASE_NAME = "contacts.db";
     62 
     63     private static final int INSERT_BATCH_SIZE = 200;
     64 
     65     /**
     66      * Estimated increase in database size after import.
     67      */
     68     private static final long DATABASE_SIZE_MULTIPLIER = 4;
     69 
     70     /**
     71      * Estimated minimum database size in megabytes.
     72      */
     73     private static final long DATABASE_MIN_SIZE = 5;
     74 
     75     private final Context mContext;
     76     private final ContactsProvider2 mContactsProvider;
     77     private final NameLookupBuilder mNameLookupBuilder;
     78 
     79     private ContactsDatabaseHelper mDbHelper;
     80     private ContentValues mValues = new ContentValues();
     81     private ContentResolver mResolver;
     82     private boolean mPhoneticNameAvailable = true;
     83 
     84     private SQLiteDatabase mSourceDb;
     85     private SQLiteDatabase mTargetDb;
     86 
     87     private NameSplitter mNameSplitter;
     88     private int mBatchCounter;
     89 
     90     private int mContactCount;
     91 
     92     private long mStructuredNameMimetypeId;
     93     private long mNoteMimetypeId;
     94     private long mOrganizationMimetypeId;
     95     private long mPhoneMimetypeId;
     96     private long mEmailMimetypeId;
     97     private long mImMimetypeId;
     98     private long mPostalMimetypeId;
     99     private long mPhotoMimetypeId;
    100     private long mGroupMembershipMimetypeId;
    101 
    102     private long mEstimatedStorageRequirement = DATABASE_MIN_SIZE;
    103 
    104     public LegacyContactImporter(Context context, ContactsProvider2 contactsProvider) {
    105         mContext = context;
    106         mContactsProvider = contactsProvider;
    107         mResolver = mContactsProvider.getContext().getContentResolver();
    108         mNameLookupBuilder = mContactsProvider.getNameLookupBuilder();
    109     }
    110 
    111     public boolean importContacts() throws Exception {
    112         String path = mContext.getDatabasePath(DATABASE_NAME).getPath();
    113         File file = new File(path);
    114         if (!file.exists()) {
    115             Log.i(TAG, "Legacy contacts database does not exist at " + path);
    116             return true;
    117         }
    118 
    119         Log.w(TAG, "Importing contacts from " + path);
    120 
    121         for (int i = 0; i < MAX_ATTEMPTS; i++) {
    122             try {
    123                 mSourceDb = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
    124                 importContactsFromLegacyDb();
    125                 Log.i(TAG, "Imported legacy contacts: " + mContactCount);
    126                 mContactsProvider.notifyChange();
    127                 return true;
    128 
    129             } catch (SQLiteException e) {
    130                 Log.e(TAG, "Database import exception. Will retry in " + DELAY_BETWEEN_ATTEMPTS
    131                         + "ms", e);
    132 
    133                 // We could get a "database locked" exception here, in which
    134                 // case we should retry
    135                 Thread.sleep(DELAY_BETWEEN_ATTEMPTS);
    136 
    137             } finally {
    138                 if (mSourceDb != null) {
    139                     mSourceDb.close();
    140                 }
    141             }
    142         }
    143 
    144         long oldDatabaseSize = file.length();
    145         mEstimatedStorageRequirement = oldDatabaseSize * DATABASE_SIZE_MULTIPLIER / 1024 / 1024;
    146         if (mEstimatedStorageRequirement < DATABASE_MIN_SIZE) {
    147             mEstimatedStorageRequirement = DATABASE_MIN_SIZE;
    148         }
    149 
    150         return false;
    151     }
    152 
    153     public long getEstimatedStorageRequirement() {
    154         return mEstimatedStorageRequirement;
    155     }
    156 
    157     private void importContactsFromLegacyDb() {
    158         int version = mSourceDb.getVersion();
    159 
    160         // Upgrade to version 78 was the latest that wiped out data.  Might as well follow suit
    161         // and ignore earlier versions.
    162         if (version < 78) {
    163             return;
    164         }
    165 
    166         if (version < 80) {
    167             mPhoneticNameAvailable = false;
    168         }
    169 
    170         mDbHelper = (ContactsDatabaseHelper)mContactsProvider.getDatabaseHelper();
    171         mTargetDb = mDbHelper.getWritableDatabase();
    172 
    173         mStructuredNameMimetypeId = mDbHelper.getMimeTypeId(StructuredName.CONTENT_ITEM_TYPE);
    174         mNoteMimetypeId = mDbHelper.getMimeTypeId(Note.CONTENT_ITEM_TYPE);
    175         mOrganizationMimetypeId = mDbHelper.getMimeTypeId(Organization.CONTENT_ITEM_TYPE);
    176         mPhoneMimetypeId = mDbHelper.getMimeTypeId(Phone.CONTENT_ITEM_TYPE);
    177         mEmailMimetypeId = mDbHelper.getMimeTypeId(Email.CONTENT_ITEM_TYPE);
    178         mImMimetypeId = mDbHelper.getMimeTypeId(Im.CONTENT_ITEM_TYPE);
    179         mPostalMimetypeId = mDbHelper.getMimeTypeId(StructuredPostal.CONTENT_ITEM_TYPE);
    180         mPhotoMimetypeId = mDbHelper.getMimeTypeId(Photo.CONTENT_ITEM_TYPE);
    181         mGroupMembershipMimetypeId =
    182                 mDbHelper.getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
    183 
    184         mNameSplitter = mContactsProvider.getNameSplitter();
    185 
    186         mTargetDb.beginTransaction();
    187         try {
    188             checkForImportFailureTest();
    189 
    190             /*
    191              * At this point there should be no data in the contacts provider, but in case
    192              * some was inserted by mistake, we should remove it. The main reason for this
    193              * is that we will be preserving original contact IDs and don't want to run into
    194              * any collisions.
    195              */
    196             mContactsProvider.wipeData();
    197 
    198             importGroups();
    199             importPeople();
    200             importOrganizations();
    201             importPhones();
    202             importContactMethods();
    203             importPhotos();
    204             importGroupMemberships();
    205             updateDisplayNamesAndLookupKeys();
    206 
    207             // Deleted contacts should be inserted after everything else, because
    208             // the legacy table does not provide an _ID field - the _ID field
    209             // will be autoincremented
    210             importDeletedPeople();
    211 
    212             mDbHelper.updateAllVisible();
    213 
    214             mTargetDb.setTransactionSuccessful();
    215         } finally {
    216             mTargetDb.endTransaction();
    217         }
    218 
    219         importCalls();
    220     }
    221 
    222     /**
    223      * This is used for simulating an import failure. Insert a row into the "settings"
    224      * table with key='TEST' and then proceed with the upgrade.  Remove the record
    225      * after verifying the failure handling.
    226      */
    227     private void checkForImportFailureTest() {
    228         long isTest = DatabaseUtils.longForQuery(mSourceDb,
    229                 "SELECT COUNT(*) FROM settings WHERE key='TEST'", null);
    230         if (isTest != 0) {
    231             throw new SQLiteException("Testing import failure.");
    232         }
    233     }
    234 
    235     private interface GroupsQuery {
    236         String TABLE = "groups";
    237 
    238         String[] COLUMNS = {
    239                 "_id", "name", "notes", "should_sync", "system_id", "_sync_account", "_sync_id",
    240                 "_sync_dirty",
    241         };
    242 
    243         static int ID = 0;
    244         static int NAME = 1;
    245         static int NOTES = 2;
    246         static int SHOULD_SYNC = 3;            // TODO add this feature to Groups
    247         static int SYSTEM_ID = 4;
    248 
    249         static int _SYNC_ACCOUNT = 5;
    250         static int _SYNC_ID = 6;
    251         static int _SYNC_DIRTY = 7;
    252     }
    253 
    254     private interface GroupsInsert {
    255         String INSERT_SQL = "INSERT INTO " + Tables.GROUPS + "(" +
    256                 Groups._ID + "," +
    257                 Groups.TITLE + "," +
    258                 Groups.NOTES + "," +
    259                 Groups.SYSTEM_ID + "," +
    260                 Groups.DIRTY + "," +
    261                 Groups.GROUP_VISIBLE + "," +
    262                 Groups.ACCOUNT_NAME + "," +
    263                 Groups.ACCOUNT_TYPE + "," +
    264                 Groups.SOURCE_ID +
    265         ") VALUES (?,?,?,?,?,?,?,?,?)";
    266 
    267         int ID = 1;
    268         int TITLE = 2;
    269         int NOTES = 3;
    270         int SYSTEM_ID = 4;
    271         int DIRTY = 5;
    272         int GROUP_VISIBLE = 6;
    273         int ACCOUNT_NAME = 7;
    274         int ACCOUNT_TYPE = 8;
    275         int SOURCE_ID = 9;
    276     }
    277 
    278     private void importGroups() {
    279         SQLiteStatement insert = mTargetDb.compileStatement(GroupsInsert.INSERT_SQL);
    280         Cursor c = mSourceDb.query(GroupsQuery.TABLE, GroupsQuery.COLUMNS, null, null,
    281                 null, null, null);
    282         try {
    283             while (c.moveToNext()) {
    284                 insertGroup(c, insert);
    285             }
    286         } finally {
    287             c.close();
    288             insert.close();
    289         }
    290     }
    291 
    292     private void insertGroup(Cursor c, SQLiteStatement insert) {
    293         long id = c.getLong(GroupsQuery.ID);
    294 
    295         insert.bindLong(GroupsInsert.ID, id);
    296         bindString(insert, GroupsInsert.TITLE, c.getString(GroupsQuery.NAME));
    297         bindString(insert, GroupsInsert.NOTES, c.getString(GroupsQuery.NOTES));
    298         bindString(insert, GroupsInsert.SYSTEM_ID, c.getString(GroupsQuery.SYSTEM_ID));
    299         insert.bindLong(GroupsInsert.DIRTY, c.getLong(GroupsQuery._SYNC_DIRTY));
    300         insert.bindLong(GroupsInsert.GROUP_VISIBLE, 1);
    301 
    302         String account = c.getString(GroupsQuery._SYNC_ACCOUNT);
    303         if (!TextUtils.isEmpty(account)) {
    304             bindString(insert, GroupsInsert.ACCOUNT_NAME, account);
    305             bindString(insert, GroupsInsert.ACCOUNT_TYPE, DEFAULT_ACCOUNT_TYPE);
    306             bindString(insert, GroupsInsert.SOURCE_ID, c.getString(GroupsQuery._SYNC_ID));
    307         } else {
    308             insert.bindNull(GroupsInsert.ACCOUNT_NAME);
    309             insert.bindNull(GroupsInsert.ACCOUNT_TYPE);
    310             insert.bindNull(GroupsInsert.SOURCE_ID);
    311         }
    312         insert(insert);
    313     }
    314 
    315     private interface PeopleQuery {
    316         String TABLE = "people";
    317 
    318         String NAME_SQL =
    319                 "(CASE WHEN (name IS NOT NULL AND name != '') "
    320                     + "THEN name "
    321                 + "ELSE "
    322                     + "(CASE WHEN primary_organization is NOT NULL THEN "
    323                         + "(SELECT company FROM organizations WHERE "
    324                             + "organizations._id = primary_organization) "
    325                     + "ELSE "
    326                         + "(CASE WHEN primary_phone IS NOT NULL THEN "
    327                             +"(SELECT number FROM phones WHERE phones._id = primary_phone) "
    328                         + "ELSE "
    329                             + "(CASE WHEN primary_email IS NOT NULL THEN "
    330                                 + "(SELECT data FROM contact_methods WHERE "
    331                                     + "contact_methods._id = primary_email) "
    332                             + "ELSE "
    333                                 + "null "
    334                             + "END) "
    335                         + "END) "
    336                     + "END) "
    337                 + "END) ";
    338 
    339 
    340         String[] COLUMNS_WITH_DISPLAY_NAME_WITHOUT_PHONETIC_NAME = {
    341                 "_id", NAME_SQL, "notes", "times_contacted", "last_time_contacted", "starred",
    342                 "primary_phone", "primary_organization", "primary_email", "custom_ringtone",
    343                 "send_to_voicemail", "_sync_account", "_sync_id", "_sync_time", "_sync_local_id",
    344                 "_sync_dirty",
    345         };
    346 
    347         String[] COLUMNS_WITH_DISPLAY_NAME_WITH_PHONETIC_NAME = {
    348                 "_id", NAME_SQL, "notes", "times_contacted", "last_time_contacted", "starred",
    349                 "primary_phone", "primary_organization", "primary_email", "custom_ringtone",
    350                 "send_to_voicemail", "_sync_account", "_sync_id", "_sync_time", "_sync_local_id",
    351                 "_sync_dirty", "phonetic_name",
    352         };
    353 
    354         String[] COLUMNS_WITHOUT_PHONETIC_NAME = {
    355                 "_id", "name", "notes", "times_contacted", "last_time_contacted", "starred",
    356                 "primary_phone", "primary_organization", "primary_email", "custom_ringtone",
    357                 "send_to_voicemail", "_sync_account", "_sync_id", "_sync_time", "_sync_local_id",
    358                 "_sync_dirty",
    359         };
    360 
    361         String[] COLUMNS_WITH_PHONETIC_NAME = {
    362                 "_id", "name", "notes", "times_contacted", "last_time_contacted", "starred",
    363                 "primary_phone", "primary_organization", "primary_email", "custom_ringtone",
    364                 "send_to_voicemail", "_sync_account", "_sync_id", "_sync_time", "_sync_local_id",
    365                 "_sync_dirty", "phonetic_name",
    366         };
    367 
    368         static int _ID = 0;
    369         static int NAME = 1;
    370         static int NOTES = 2;
    371         static int TIMES_CONTACTED = 3;
    372         static int LAST_TIME_CONTACTED = 4;
    373         static int STARRED = 5;
    374         static int PRIMARY_PHONE = 6;
    375         static int PRIMARY_ORGANIZATION = 7;
    376         static int PRIMARY_EMAIL = 8;
    377         static int CUSTOM_RINGTONE = 9;
    378         static int SEND_TO_VOICEMAIL = 10;
    379 
    380         static int _SYNC_ACCOUNT = 11;
    381         static int _SYNC_ID = 12;
    382         static int _SYNC_TIME = 13;
    383         static int _SYNC_LOCAL_ID = 14;
    384         static int _SYNC_DIRTY = 15;
    385 
    386         static int PHONETIC_NAME = 16;
    387     }
    388 
    389 
    390     private interface RawContactsInsert {
    391         String INSERT_SQL = "INSERT INTO " + Tables.RAW_CONTACTS + "(" +
    392                 RawContacts._ID + "," +
    393                 RawContacts.CONTACT_ID + "," +
    394                 RawContacts.CUSTOM_RINGTONE + "," +
    395                 RawContacts.DIRTY + "," +
    396                 RawContacts.LAST_TIME_CONTACTED + "," +
    397                 RawContacts.SEND_TO_VOICEMAIL + "," +
    398                 RawContacts.STARRED + "," +
    399                 RawContacts.TIMES_CONTACTED + "," +
    400                 RawContacts.SYNC1 + "," +
    401                 RawContacts.SYNC2 + "," +
    402                 RawContacts.ACCOUNT_NAME + "," +
    403                 RawContacts.ACCOUNT_TYPE + "," +
    404                 RawContacts.SOURCE_ID + "," +
    405                 RawContactsColumns.DISPLAY_NAME +
    406          ") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
    407 
    408         int ID = 1;
    409         int CONTACT_ID = 2;
    410         int CUSTOM_RINGTONE = 3;
    411         int DIRTY = 4;
    412         int LAST_TIME_CONTACTED = 5;
    413         int SEND_TO_VOICEMAIL = 6;
    414         int STARRED = 7;
    415         int TIMES_CONTACTED = 8;
    416         int SYNC1 = 9;
    417         int SYNC2 = 10;
    418         int ACCOUNT_NAME = 11;
    419         int ACCOUNT_TYPE = 12;
    420         int SOURCE_ID = 13;
    421         int DISPLAY_NAME = 14;
    422     }
    423 
    424     private interface ContactsInsert {
    425         String INSERT_SQL = "INSERT INTO " + Tables.CONTACTS + "(" +
    426                 Contacts._ID + "," +
    427                 Contacts.CUSTOM_RINGTONE + "," +
    428                 Contacts.LAST_TIME_CONTACTED + "," +
    429                 Contacts.SEND_TO_VOICEMAIL + "," +
    430                 Contacts.STARRED + "," +
    431                 Contacts.TIMES_CONTACTED + "," +
    432                 Contacts.NAME_RAW_CONTACT_ID +
    433          ") VALUES (?,?,?,?,?,?,?)";
    434 
    435         int ID = 1;
    436         int CUSTOM_RINGTONE = 2;
    437         int LAST_TIME_CONTACTED = 3;
    438         int SEND_TO_VOICEMAIL = 4;
    439         int STARRED = 5;
    440         int TIMES_CONTACTED = 6;
    441         int NAME_RAW_CONTACT_ID = 7;
    442     }
    443 
    444     private interface StructuredNameInsert {
    445         String INSERT_SQL = "INSERT INTO " + Tables.DATA + "(" +
    446                 Data.RAW_CONTACT_ID + "," +
    447                 DataColumns.MIMETYPE_ID + "," +
    448                 StructuredName.DISPLAY_NAME + "," +
    449                 StructuredName.PREFIX + "," +
    450                 StructuredName.GIVEN_NAME + "," +
    451                 StructuredName.MIDDLE_NAME + "," +
    452                 StructuredName.FAMILY_NAME + "," +
    453                 StructuredName.SUFFIX + "," +
    454                 StructuredName.FULL_NAME_STYLE + "," +
    455                 StructuredName.PHONETIC_FAMILY_NAME + "," +
    456                 StructuredName.PHONETIC_MIDDLE_NAME + "," +
    457                 StructuredName.PHONETIC_GIVEN_NAME + "," +
    458                 StructuredName.PHONETIC_NAME_STYLE +
    459          ") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)";
    460 
    461         int RAW_CONTACT_ID = 1;
    462         int MIMETYPE_ID = 2;
    463         int DISPLAY_NAME = 3;
    464         int PREFIX = 4;
    465         int GIVEN_NAME = 5;
    466         int MIDDLE_NAME = 6;
    467         int FAMILY_NAME = 7;
    468         int SUFFIX = 8;
    469         int FULL_NAME_STYLE = 9;
    470         int PHONETIC_FAMILY_NAME = 10;
    471         int PHONETIC_MIDDLE_NAME = 11;
    472         int PHONETIC_GIVEN_NAME = 12;
    473         int PHONETIC_NAME_STYLE = 13;
    474     }
    475 
    476     private interface NoteInsert {
    477         String INSERT_SQL = "INSERT INTO " + Tables.DATA + "(" +
    478                 Data.RAW_CONTACT_ID + "," +
    479                 DataColumns.MIMETYPE_ID + "," +
    480                 Note.NOTE +
    481          ") VALUES (?,?,?)";
    482 
    483         int RAW_CONTACT_ID = 1;
    484         int MIMETYPE_ID = 2;
    485         int NOTE = 3;
    486     }
    487 
    488     private void importPeople() {
    489         SQLiteStatement rawContactInsert = mTargetDb.compileStatement(RawContactsInsert.INSERT_SQL);
    490         SQLiteStatement contactInsert = mTargetDb.compileStatement(ContactsInsert.INSERT_SQL);
    491         SQLiteStatement structuredNameInsert =
    492                 mTargetDb.compileStatement(StructuredNameInsert.INSERT_SQL);
    493         SQLiteStatement noteInsert = mTargetDb.compileStatement(NoteInsert.INSERT_SQL);
    494         try {
    495             String[] columns = mPhoneticNameAvailable
    496                     ? PeopleQuery.COLUMNS_WITH_DISPLAY_NAME_WITH_PHONETIC_NAME
    497                     : PeopleQuery.COLUMNS_WITH_DISPLAY_NAME_WITHOUT_PHONETIC_NAME;
    498             Cursor c = mSourceDb.query(PeopleQuery.TABLE, columns, "name IS NULL", null, null,
    499                     null, null);
    500             try {
    501                 while (c.moveToNext()) {
    502                     insertRawContact(c, rawContactInsert);
    503                     insertContact(c, contactInsert);
    504                     insertNote(c, noteInsert);
    505                     mContactCount++;
    506                 }
    507             } finally {
    508                 c.close();
    509             }
    510 
    511             columns = mPhoneticNameAvailable
    512                     ? PeopleQuery.COLUMNS_WITH_PHONETIC_NAME
    513                     : PeopleQuery.COLUMNS_WITHOUT_PHONETIC_NAME;
    514             c = mSourceDb.query(PeopleQuery.TABLE, columns, "name IS NOT NULL", null, null, null,
    515                     null);
    516             try {
    517                 while (c.moveToNext()) {
    518                     long id = insertRawContact(c, rawContactInsert);
    519                     insertContact(c, contactInsert);
    520                     insertStructuredName(c, structuredNameInsert);
    521                     insertNote(c, noteInsert);
    522                     mContactCount++;
    523                 }
    524             } finally {
    525                 c.close();
    526             }
    527         } finally {
    528             rawContactInsert.close();
    529             contactInsert.close();
    530             structuredNameInsert.close();
    531             noteInsert.close();
    532         }
    533     }
    534 
    535     private long insertRawContact(Cursor c, SQLiteStatement insert) {
    536         long id = c.getLong(PeopleQuery._ID);
    537         insert.bindLong(RawContactsInsert.ID, id);
    538         insert.bindLong(RawContactsInsert.CONTACT_ID, id);
    539         bindString(insert, RawContactsInsert.CUSTOM_RINGTONE,
    540                 c.getString(PeopleQuery.CUSTOM_RINGTONE));
    541         bindString(insert, RawContactsInsert.DIRTY,
    542                 c.getString(PeopleQuery._SYNC_DIRTY));
    543         insert.bindLong(RawContactsInsert.LAST_TIME_CONTACTED,
    544                 c.getLong(PeopleQuery.LAST_TIME_CONTACTED));
    545         insert.bindLong(RawContactsInsert.SEND_TO_VOICEMAIL,
    546                 c.getLong(PeopleQuery.SEND_TO_VOICEMAIL));
    547         insert.bindLong(RawContactsInsert.STARRED,
    548                 c.getLong(PeopleQuery.STARRED));
    549         insert.bindLong(RawContactsInsert.TIMES_CONTACTED,
    550                 c.getLong(PeopleQuery.TIMES_CONTACTED));
    551         bindString(insert, RawContactsInsert.SYNC1,
    552                 c.getString(PeopleQuery._SYNC_TIME));
    553         bindString(insert, RawContactsInsert.SYNC2,
    554                 c.getString(PeopleQuery._SYNC_LOCAL_ID));
    555         bindString(insert, RawContactsInsert.DISPLAY_NAME,
    556                 c.getString(PeopleQuery.NAME));
    557 
    558         String account = c.getString(PeopleQuery._SYNC_ACCOUNT);
    559         if (!TextUtils.isEmpty(account)) {
    560             bindString(insert, RawContactsInsert.ACCOUNT_NAME, account);
    561             bindString(insert, RawContactsInsert.ACCOUNT_TYPE, DEFAULT_ACCOUNT_TYPE);
    562             bindString(insert, RawContactsInsert.SOURCE_ID, c.getString(PeopleQuery._SYNC_ID));
    563         } else {
    564             insert.bindNull(RawContactsInsert.ACCOUNT_NAME);
    565             insert.bindNull(RawContactsInsert.ACCOUNT_TYPE);
    566             insert.bindNull(RawContactsInsert.SOURCE_ID);
    567         }
    568         insert(insert);
    569         return id;
    570     }
    571 
    572     private void insertContact(Cursor c, SQLiteStatement insert) {
    573         long id = c.getLong(PeopleQuery._ID);
    574         insert.bindLong(ContactsInsert.ID, id);
    575         bindString(insert, ContactsInsert.CUSTOM_RINGTONE,
    576                 c.getString(PeopleQuery.CUSTOM_RINGTONE));
    577         insert.bindLong(ContactsInsert.LAST_TIME_CONTACTED,
    578                 c.getLong(PeopleQuery.LAST_TIME_CONTACTED));
    579         insert.bindLong(ContactsInsert.SEND_TO_VOICEMAIL,
    580                 c.getLong(PeopleQuery.SEND_TO_VOICEMAIL));
    581         insert.bindLong(ContactsInsert.STARRED,
    582                 c.getLong(PeopleQuery.STARRED));
    583         insert.bindLong(ContactsInsert.TIMES_CONTACTED,
    584                 c.getLong(PeopleQuery.TIMES_CONTACTED));
    585         insert.bindLong(ContactsInsert.NAME_RAW_CONTACT_ID, id);
    586 
    587         insert(insert);
    588     }
    589 
    590     private void insertStructuredName(Cursor c, SQLiteStatement insert) {
    591         String name = c.getString(PeopleQuery.NAME);
    592         if (TextUtils.isEmpty(name)) {
    593             return;
    594         }
    595 
    596         long id = c.getLong(PeopleQuery._ID);
    597 
    598         insert.bindLong(StructuredNameInsert.RAW_CONTACT_ID, id);
    599         insert.bindLong(StructuredNameInsert.MIMETYPE_ID, mStructuredNameMimetypeId);
    600         bindString(insert, StructuredNameInsert.DISPLAY_NAME, name);
    601 
    602         NameSplitter.Name splitName = new NameSplitter.Name();
    603         mNameSplitter.split(splitName, name);
    604 
    605         bindString(insert, StructuredNameInsert.PREFIX,
    606                 splitName.getPrefix());
    607         bindString(insert, StructuredNameInsert.GIVEN_NAME,
    608                 splitName.getGivenNames());
    609         bindString(insert, StructuredNameInsert.MIDDLE_NAME,
    610                 splitName.getMiddleName());
    611         bindString(insert, StructuredNameInsert.FAMILY_NAME,
    612                 splitName.getFamilyName());
    613         bindString(insert, StructuredNameInsert.SUFFIX,
    614                 splitName.getSuffix());
    615         final String joined = mNameSplitter.join(splitName, true, true);
    616         bindString(insert, StructuredNameInsert.DISPLAY_NAME, joined);
    617 
    618         if (mPhoneticNameAvailable) {
    619             String phoneticName = c.getString(PeopleQuery.PHONETIC_NAME);
    620             if (phoneticName != null) {
    621                 int index = phoneticName.indexOf(' ');
    622                 if (index == -1) {
    623                     splitName.phoneticFamilyName = phoneticName;
    624                 } else {
    625                     splitName.phoneticFamilyName = phoneticName.substring(0, index).trim();
    626                     splitName.phoneticGivenName = phoneticName.substring(index + 1).trim();
    627                 }
    628             }
    629         }
    630 
    631         mNameSplitter.guessNameStyle(splitName);
    632 
    633         int fullNameStyle = splitName.getFullNameStyle();
    634         insert.bindLong(StructuredNameInsert.FULL_NAME_STYLE,
    635                 fullNameStyle);
    636         bindString(insert, StructuredNameInsert.PHONETIC_FAMILY_NAME,
    637                 splitName.phoneticFamilyName);
    638         bindString(insert, StructuredNameInsert.PHONETIC_MIDDLE_NAME,
    639                 splitName.phoneticMiddleName);
    640         bindString(insert, StructuredNameInsert.PHONETIC_GIVEN_NAME,
    641                 splitName.phoneticGivenName);
    642         insert.bindLong(StructuredNameInsert.PHONETIC_NAME_STYLE,
    643                 splitName.phoneticNameStyle);
    644 
    645         long dataId = insert(insert);
    646 
    647         mNameLookupBuilder.insertNameLookup(id, dataId, name,
    648                 mNameSplitter.getAdjustedFullNameStyle(fullNameStyle));
    649 
    650         if (splitName.phoneticFamilyName != null
    651                 || splitName.phoneticMiddleName != null
    652                 || splitName.phoneticGivenName != null) {
    653             mDbHelper.insertNameLookupForPhoneticName(id, dataId,
    654                     splitName.phoneticFamilyName,
    655                     splitName.phoneticMiddleName,
    656                     splitName.phoneticGivenName);
    657         }
    658     }
    659 
    660     private void insertNote(Cursor c, SQLiteStatement insert) {
    661         String notes = c.getString(PeopleQuery.NOTES);
    662 
    663         if (TextUtils.isEmpty(notes)) {
    664             return;
    665         }
    666 
    667         long id = c.getLong(PeopleQuery._ID);
    668         insert.bindLong(NoteInsert.RAW_CONTACT_ID, id);
    669         insert.bindLong(NoteInsert.MIMETYPE_ID, mNoteMimetypeId);
    670         bindString(insert, NoteInsert.NOTE, notes);
    671         insert(insert);
    672     }
    673 
    674     private interface OrganizationsQuery {
    675         String TABLE = "organizations";
    676 
    677         String[] COLUMNS = {
    678                 "person", "company", "title", "isprimary", "type", "label",
    679         };
    680 
    681         static int PERSON = 0;
    682         static int COMPANY = 1;
    683         static int TITLE = 2;
    684         static int ISPRIMARY = 3;
    685         static int TYPE = 4;
    686         static int LABEL = 5;
    687     }
    688 
    689     private interface OrganizationInsert {
    690         String INSERT_SQL = "INSERT INTO " + Tables.DATA + "(" +
    691                 Data.RAW_CONTACT_ID + "," +
    692                 DataColumns.MIMETYPE_ID + "," +
    693                 Data.IS_PRIMARY + "," +
    694                 Data.IS_SUPER_PRIMARY + "," +
    695                 Organization.COMPANY + "," +
    696                 Organization.TITLE + "," +
    697                 Organization.TYPE + "," +
    698                 Organization.LABEL +
    699          ") VALUES (?,?,?,?,?,?,?,?)";
    700 
    701         int RAW_CONTACT_ID = 1;
    702         int MIMETYPE_ID = 2;
    703         int IS_PRIMARY = 3;
    704         int IS_SUPER_PRIMARY = 4;
    705         int COMPANY = 5;
    706         int TITLE = 6;
    707         int TYPE = 7;
    708         int LABEL = 8;
    709     }
    710 
    711     private void importOrganizations() {
    712         SQLiteStatement insert = mTargetDb.compileStatement(OrganizationInsert.INSERT_SQL);
    713         Cursor c = mSourceDb.query(OrganizationsQuery.TABLE, OrganizationsQuery.COLUMNS, null, null,
    714                 null, null, null);
    715         try {
    716             while (c.moveToNext()) {
    717                 insertOrganization(c, insert);
    718             }
    719         } finally {
    720             c.close();
    721             insert.close();
    722         }
    723     }
    724 
    725     private void insertOrganization(Cursor c, SQLiteStatement insert) {
    726         long id = c.getLong(OrganizationsQuery.PERSON);
    727         insert.bindLong(OrganizationInsert.RAW_CONTACT_ID, id);
    728         insert.bindLong(OrganizationInsert.MIMETYPE_ID, mOrganizationMimetypeId);
    729         bindString(insert, OrganizationInsert.IS_PRIMARY, c.getString(OrganizationsQuery.ISPRIMARY));
    730         bindString(insert, OrganizationInsert.IS_SUPER_PRIMARY,
    731                 c.getString(OrganizationsQuery.ISPRIMARY));
    732         bindString(insert, OrganizationInsert.COMPANY, c.getString(OrganizationsQuery.COMPANY));
    733         bindString(insert, OrganizationInsert.TITLE, c.getString(OrganizationsQuery.TITLE));
    734         bindString(insert, OrganizationInsert.TYPE, c.getString(OrganizationsQuery.TYPE));
    735         bindString(insert, OrganizationInsert.LABEL, c.getString(OrganizationsQuery.LABEL));
    736         insert(insert);
    737     }
    738 
    739     private interface ContactMethodsQuery {
    740         String TABLE = "contact_methods";
    741 
    742         String[] COLUMNS = {
    743                 "person", "kind", "data", "aux_data", "type", "label", "isprimary",
    744         };
    745 
    746         static int PERSON = 0;
    747         static int KIND = 1;
    748         static int DATA = 2;
    749         static int AUX_DATA = 3;
    750         static int TYPE = 4;
    751         static int LABEL = 5;
    752         static int ISPRIMARY = 6;
    753     }
    754 
    755     private interface EmailInsert {
    756         String INSERT_SQL = "INSERT INTO " + Tables.DATA + "(" +
    757                 Data.RAW_CONTACT_ID + "," +
    758                 DataColumns.MIMETYPE_ID + "," +
    759                 Data.IS_PRIMARY + "," +
    760                 Data.IS_SUPER_PRIMARY + "," +
    761                 Email.DATA + "," +
    762                 Email.TYPE + "," +
    763                 Email.LABEL + "," +
    764                 Data.DATA14 +
    765          ") VALUES (?,?,?,?,?,?,?,?)";
    766 
    767         int RAW_CONTACT_ID = 1;
    768         int MIMETYPE_ID = 2;
    769         int IS_PRIMARY = 3;
    770         int IS_SUPER_PRIMARY = 4;
    771         int DATA = 5;
    772         int TYPE = 6;
    773         int LABEL = 7;
    774         int AUX_DATA = 8;
    775     }
    776 
    777     private interface ImInsert {
    778         String INSERT_SQL = "INSERT INTO " + Tables.DATA + "(" +
    779                 Data.RAW_CONTACT_ID + "," +
    780                 DataColumns.MIMETYPE_ID + "," +
    781                 Data.IS_PRIMARY + "," +
    782                 Data.IS_SUPER_PRIMARY + "," +
    783                 Im.DATA + "," +
    784                 Im.TYPE + "," +
    785                 Im.LABEL + "," +
    786                 Data.DATA14 +
    787          ") VALUES (?,?,?,?,?,?,?,?)";
    788 
    789         int RAW_CONTACT_ID = 1;
    790         int MIMETYPE_ID = 2;
    791         int IS_PRIMARY = 3;
    792         int IS_SUPER_PRIMARY = 4;
    793         int DATA = 5;
    794         int TYPE = 6;
    795         int LABEL = 7;
    796         int AUX_DATA = 8;
    797     }
    798 
    799     private interface PostalInsert {
    800         String INSERT_SQL = "INSERT INTO " + Tables.DATA + "(" +
    801                 Data.RAW_CONTACT_ID + "," +
    802                 DataColumns.MIMETYPE_ID + "," +
    803                 Data.IS_PRIMARY + "," +
    804                 Data.IS_SUPER_PRIMARY + "," +
    805                 StructuredPostal.FORMATTED_ADDRESS + "," +
    806                 StructuredPostal.TYPE + "," +
    807                 StructuredPostal.LABEL + "," +
    808                 Data.DATA14 +
    809          ") VALUES (?,?,?,?,?,?,?,?)";
    810 
    811         int RAW_CONTACT_ID = 1;
    812         int MIMETYPE_ID = 2;
    813         int IS_PRIMARY = 3;
    814         int IS_SUPER_PRIMARY = 4;
    815         int DATA = 5;
    816         int TYPE = 6;
    817         int LABEL = 7;
    818         int AUX_DATA = 8;
    819     }
    820 
    821     private void importContactMethods() {
    822         SQLiteStatement emailInsert = mTargetDb.compileStatement(EmailInsert.INSERT_SQL);
    823         SQLiteStatement imInsert = mTargetDb.compileStatement(ImInsert.INSERT_SQL);
    824         SQLiteStatement postalInsert = mTargetDb.compileStatement(PostalInsert.INSERT_SQL);
    825         Cursor c = mSourceDb.query(ContactMethodsQuery.TABLE, ContactMethodsQuery.COLUMNS, null,
    826                 null, null, null, null);
    827         try {
    828             while (c.moveToNext()) {
    829                 int kind = c.getInt(ContactMethodsQuery.KIND);
    830                 switch (kind) {
    831                     case android.provider.Contacts.KIND_EMAIL:
    832                         insertEmail(c, emailInsert);
    833                         break;
    834 
    835                     case android.provider.Contacts.KIND_IM:
    836                         insertIm(c, imInsert);
    837                         break;
    838 
    839                     case android.provider.Contacts.KIND_POSTAL:
    840                         insertPostal(c, postalInsert);
    841                         break;
    842                 }
    843             }
    844         } finally {
    845             c.close();
    846             emailInsert.close();
    847             imInsert.close();
    848             postalInsert.close();
    849         }
    850 
    851     }
    852 
    853     private void insertEmail(Cursor c, SQLiteStatement insert) {
    854         long personId = c.getLong(ContactMethodsQuery.PERSON);
    855         String email = c.getString(ContactMethodsQuery.DATA);
    856 
    857         insert.bindLong(EmailInsert.RAW_CONTACT_ID, personId);
    858         insert.bindLong(EmailInsert.MIMETYPE_ID, mEmailMimetypeId);
    859         bindString(insert, EmailInsert.IS_PRIMARY, c.getString(ContactMethodsQuery.ISPRIMARY));
    860         bindString(insert, EmailInsert.IS_SUPER_PRIMARY, c.getString(ContactMethodsQuery.ISPRIMARY));
    861         bindString(insert, EmailInsert.DATA, email);
    862         bindString(insert, EmailInsert.AUX_DATA, c.getString(ContactMethodsQuery.AUX_DATA));
    863         bindString(insert, EmailInsert.TYPE, c.getString(ContactMethodsQuery.TYPE));
    864         bindString(insert, EmailInsert.LABEL, c.getString(ContactMethodsQuery.LABEL));
    865 
    866         long dataId = insert(insert);
    867         mDbHelper.insertNameLookupForEmail(personId, dataId, email);
    868     }
    869 
    870     private void insertIm(Cursor c, SQLiteStatement insert) {
    871         long personId = c.getLong(ContactMethodsQuery.PERSON);
    872 
    873         insert.bindLong(ImInsert.RAW_CONTACT_ID, personId);
    874         insert.bindLong(ImInsert.MIMETYPE_ID, mImMimetypeId);
    875         bindString(insert, ImInsert.IS_PRIMARY, c.getString(ContactMethodsQuery.ISPRIMARY));
    876         bindString(insert, ImInsert.IS_SUPER_PRIMARY, c.getString(ContactMethodsQuery.ISPRIMARY));
    877         bindString(insert, ImInsert.DATA, c.getString(ContactMethodsQuery.DATA));
    878         bindString(insert, ImInsert.AUX_DATA, c.getString(ContactMethodsQuery.AUX_DATA));
    879         bindString(insert, ImInsert.TYPE, c.getString(ContactMethodsQuery.TYPE));
    880         bindString(insert, ImInsert.LABEL, c.getString(ContactMethodsQuery.LABEL));
    881         insert(insert);
    882     }
    883 
    884     private void insertPostal(Cursor c, SQLiteStatement insert) {
    885         long personId = c.getLong(ContactMethodsQuery.PERSON);
    886 
    887         insert.bindLong(PostalInsert.RAW_CONTACT_ID, personId);
    888         insert.bindLong(PostalInsert.MIMETYPE_ID, mPostalMimetypeId);
    889         bindString(insert, PostalInsert.IS_PRIMARY, c.getString(ContactMethodsQuery.ISPRIMARY));
    890         bindString(insert, PostalInsert.IS_SUPER_PRIMARY,
    891                 c.getString(ContactMethodsQuery.ISPRIMARY));
    892         bindString(insert, PostalInsert.DATA, c.getString(ContactMethodsQuery.DATA));
    893         bindString(insert, PostalInsert.AUX_DATA, c.getString(ContactMethodsQuery.AUX_DATA));
    894         bindString(insert, PostalInsert.TYPE, c.getString(ContactMethodsQuery.TYPE));
    895         bindString(insert, PostalInsert.LABEL, c.getString(ContactMethodsQuery.LABEL));
    896         insert(insert);
    897     }
    898 
    899     private interface PhonesQuery {
    900         String TABLE = "phones";
    901 
    902         String[] COLUMNS = {
    903                 "person", "type", "number", "label", "isprimary",
    904         };
    905 
    906         static int PERSON = 0;
    907         static int TYPE = 1;
    908         static int NUMBER = 2;
    909         static int LABEL = 3;
    910         static int ISPRIMARY = 4;
    911     }
    912 
    913     private interface PhoneInsert {
    914         String INSERT_SQL = "INSERT INTO " + Tables.DATA + "(" +
    915                 Data.RAW_CONTACT_ID + "," +
    916                 DataColumns.MIMETYPE_ID + "," +
    917                 Data.IS_PRIMARY + "," +
    918                 Data.IS_SUPER_PRIMARY + "," +
    919                 Phone.NUMBER + "," +
    920                 Phone.TYPE + "," +
    921                 Phone.LABEL + "," +
    922                 PhoneColumns.NORMALIZED_NUMBER +
    923          ") VALUES (?,?,?,?,?,?,?,?)";
    924 
    925         int RAW_CONTACT_ID = 1;
    926         int MIMETYPE_ID = 2;
    927         int IS_PRIMARY = 3;
    928         int IS_SUPER_PRIMARY = 4;
    929         int NUMBER = 5;
    930         int TYPE = 6;
    931         int LABEL = 7;
    932         int NORMALIZED_NUMBER = 8;
    933     }
    934 
    935     private interface PhoneLookupInsert {
    936         String INSERT_SQL = "INSERT INTO " + Tables.PHONE_LOOKUP + "(" +
    937                 PhoneLookupColumns.RAW_CONTACT_ID + "," +
    938                 PhoneLookupColumns.DATA_ID + "," +
    939                 PhoneLookupColumns.NORMALIZED_NUMBER + "," +
    940                 PhoneLookupColumns.MIN_MATCH +
    941          ") VALUES (?,?,?,?)";
    942 
    943         int RAW_CONTACT_ID = 1;
    944         int DATA_ID = 2;
    945         int NORMALIZED_NUMBER = 3;
    946         int MIN_MATCH = 4;
    947     }
    948 
    949     private interface HasPhoneNumberUpdate {
    950         String UPDATE_SQL = "UPDATE " + Tables.CONTACTS +
    951                 " SET " + Contacts.HAS_PHONE_NUMBER + "=1 WHERE " + Contacts._ID + "=?";
    952 
    953         int CONTACT_ID = 1;
    954     }
    955 
    956     private void importPhones() {
    957         SQLiteStatement phoneInsert = mTargetDb.compileStatement(PhoneInsert.INSERT_SQL);
    958         SQLiteStatement phoneLookupInsert =
    959                 mTargetDb.compileStatement(PhoneLookupInsert.INSERT_SQL);
    960         SQLiteStatement hasPhoneUpdate =
    961                 mTargetDb.compileStatement(HasPhoneNumberUpdate.UPDATE_SQL);
    962         Cursor c = mSourceDb.query(PhonesQuery.TABLE, PhonesQuery.COLUMNS, null, null,
    963                 null, null, null);
    964         try {
    965             while (c.moveToNext()) {
    966                 insertPhone(c, phoneInsert, phoneLookupInsert, hasPhoneUpdate);
    967             }
    968         } finally {
    969             c.close();
    970             phoneInsert.close();
    971             phoneLookupInsert.close();
    972             hasPhoneUpdate.close();
    973         }
    974     }
    975 
    976     private void insertPhone(Cursor c, SQLiteStatement phoneInsert,
    977             SQLiteStatement phoneLookupInsert, SQLiteStatement hasPhoneUpdate) {
    978         long lastUpdatedContact = -1;
    979         long id = c.getLong(PhonesQuery.PERSON);
    980         String number = c.getString(PhonesQuery.NUMBER);
    981         String normalizedNumber = null;
    982         if (number != null) {
    983             normalizedNumber = PhoneNumberUtils.getStrippedReversed(number);
    984         }
    985         phoneInsert.bindLong(PhoneInsert.RAW_CONTACT_ID, id);
    986         phoneInsert.bindLong(PhoneInsert.MIMETYPE_ID, mPhoneMimetypeId);
    987         bindString(phoneInsert, PhoneInsert.IS_PRIMARY, c.getString(PhonesQuery.ISPRIMARY));
    988         bindString(phoneInsert, PhoneInsert.IS_SUPER_PRIMARY, c.getString(PhonesQuery.ISPRIMARY));
    989         bindString(phoneInsert, PhoneInsert.NUMBER, number);
    990         bindString(phoneInsert, PhoneInsert.TYPE, c.getString(PhonesQuery.TYPE));
    991         bindString(phoneInsert, PhoneInsert.LABEL, c.getString(PhonesQuery.LABEL));
    992         bindString(phoneInsert, PhoneInsert.NORMALIZED_NUMBER, normalizedNumber);
    993 
    994         long dataId = insert(phoneInsert);
    995         if (normalizedNumber != null) {
    996             phoneLookupInsert.bindLong(PhoneLookupInsert.RAW_CONTACT_ID, id);
    997             phoneLookupInsert.bindLong(PhoneLookupInsert.DATA_ID, dataId);
    998             phoneLookupInsert.bindString(PhoneLookupInsert.NORMALIZED_NUMBER, normalizedNumber);
    999             phoneLookupInsert.bindString(PhoneLookupInsert.MIN_MATCH,
   1000                     PhoneNumberUtils.toCallerIDMinMatch(number));
   1001             insert(phoneLookupInsert);
   1002 
   1003             if (lastUpdatedContact != id) {
   1004                 lastUpdatedContact = id;
   1005                 hasPhoneUpdate.bindLong(HasPhoneNumberUpdate.CONTACT_ID, id);
   1006                 hasPhoneUpdate.execute();
   1007             }
   1008         }
   1009     }
   1010 
   1011     private interface PhotosQuery {
   1012         String TABLE = "photos";
   1013 
   1014         String[] COLUMNS = {
   1015                 "person", "data", "_sync_id", "_sync_account"
   1016         };
   1017 
   1018         static int PERSON = 0;
   1019         static int DATA = 1;
   1020         static int _SYNC_ID = 2;
   1021         static int _SYNC_ACCOUNT = 3;
   1022     }
   1023 
   1024     private interface PhotoInsert {
   1025         String INSERT_SQL = "INSERT INTO " + Tables.DATA + "(" +
   1026                 Data.RAW_CONTACT_ID + "," +
   1027                 DataColumns.MIMETYPE_ID + "," +
   1028                 Photo.PHOTO + "," +
   1029                 Data.SYNC1 +
   1030          ") VALUES (?,?,?,?)";
   1031 
   1032         int RAW_CONTACT_ID = 1;
   1033         int MIMETYPE_ID = 2;
   1034         int PHOTO = 3;
   1035         int SYNC1 = 4;
   1036     }
   1037 
   1038     private interface PhotoIdUpdate {
   1039         String UPDATE_SQL = "UPDATE " + Tables.CONTACTS +
   1040                 " SET " + Contacts.PHOTO_ID + "=? WHERE " + Contacts._ID + "=?";
   1041 
   1042         int PHOTO_ID = 1;
   1043         int CONTACT_ID = 2;
   1044     }
   1045 
   1046     private void importPhotos() {
   1047         SQLiteStatement insert = mTargetDb.compileStatement(PhotoInsert.INSERT_SQL);
   1048         SQLiteStatement photoIdUpdate = mTargetDb.compileStatement(PhotoIdUpdate.UPDATE_SQL);
   1049         Cursor c = mSourceDb.query(PhotosQuery.TABLE, PhotosQuery.COLUMNS, null, null,
   1050                 null, null, null);
   1051         try {
   1052             while (c.moveToNext()) {
   1053                 insertPhoto(c, insert, photoIdUpdate);
   1054             }
   1055         } finally {
   1056             c.close();
   1057             insert.close();
   1058             photoIdUpdate.close();
   1059         }
   1060     }
   1061 
   1062     private void insertPhoto(Cursor c, SQLiteStatement insert, SQLiteStatement photoIdUpdate) {
   1063         if (c.isNull(PhotosQuery.DATA)) {
   1064             return;
   1065         }
   1066 
   1067         long personId = c.getLong(PhotosQuery.PERSON);
   1068 
   1069         insert.bindLong(PhotoInsert.RAW_CONTACT_ID, personId);
   1070         insert.bindLong(PhotoInsert.MIMETYPE_ID, mPhotoMimetypeId);
   1071         insert.bindBlob(PhotoInsert.PHOTO, c.getBlob(PhotosQuery.DATA));
   1072 
   1073         String account = c.getString(PhotosQuery._SYNC_ACCOUNT);
   1074         if (!TextUtils.isEmpty(account)) {
   1075             bindString(insert, PhotoInsert.SYNC1, c.getString(PhotosQuery._SYNC_ID));
   1076         } else {
   1077             insert.bindNull(PhotoInsert.SYNC1);
   1078         }
   1079 
   1080         long rowId = insert(insert);
   1081         photoIdUpdate.bindLong(PhotoIdUpdate.PHOTO_ID, rowId);
   1082         photoIdUpdate.bindLong(PhotoIdUpdate.CONTACT_ID, personId);
   1083         photoIdUpdate.execute();
   1084     }
   1085 
   1086     private interface GroupMembershipQuery {
   1087         String TABLE = "groupmembership";
   1088 
   1089         String[] COLUMNS = {
   1090                 "person", "group_id", "group_sync_account", "group_sync_id"
   1091         };
   1092 
   1093         static int PERSON_ID = 0;
   1094         static int GROUP_ID = 1;
   1095         static int GROUP_SYNC_ACCOUNT = 2;
   1096         static int GROUP_SYNC_ID = 3;
   1097     }
   1098 
   1099     private interface GroupMembershipInsert {
   1100         String INSERT_SQL = "INSERT INTO " + Tables.DATA + "(" +
   1101                 Data.RAW_CONTACT_ID + "," +
   1102                 DataColumns.MIMETYPE_ID + "," +
   1103                 GroupMembership.GROUP_ROW_ID +
   1104          ") VALUES (?,?,?)";
   1105 
   1106         int RAW_CONTACT_ID = 1;
   1107         int MIMETYPE_ID = 2;
   1108         int GROUP_ROW_ID = 3;
   1109     }
   1110 
   1111     private void importGroupMemberships() {
   1112         SQLiteStatement insert = mTargetDb.compileStatement(GroupMembershipInsert.INSERT_SQL);
   1113         Cursor c = mSourceDb.query(GroupMembershipQuery.TABLE, GroupMembershipQuery.COLUMNS, null,
   1114                 null, null, null, null);
   1115         try {
   1116             while (c.moveToNext()) {
   1117                 insertGroupMembership(c, insert);
   1118             }
   1119         } finally {
   1120             c.close();
   1121             insert.close();
   1122         }
   1123     }
   1124 
   1125     private void insertGroupMembership(Cursor c, SQLiteStatement insert) {
   1126         long personId = c.getLong(GroupMembershipQuery.PERSON_ID);
   1127 
   1128         long groupId = 0;
   1129         if (c.isNull(GroupMembershipQuery.GROUP_ID)) {
   1130             String account = c.getString(GroupMembershipQuery.GROUP_SYNC_ACCOUNT);
   1131             if (!TextUtils.isEmpty(account)) {
   1132                 String syncId = c.getString(GroupMembershipQuery.GROUP_SYNC_ID);
   1133 
   1134                 Cursor cursor = mTargetDb.query(Tables.GROUPS,
   1135                         new String[]{Groups._ID}, Groups.SOURCE_ID + "=?", new String[]{syncId},
   1136                         null, null, null);
   1137                 try {
   1138                     if (cursor.moveToFirst()) {
   1139                         groupId = cursor.getLong(0);
   1140                     }
   1141                 } finally {
   1142                     cursor.close();
   1143                 }
   1144 
   1145                 if (groupId == 0) {
   1146                     ContentValues values = new ContentValues();
   1147                     values.put(Groups.ACCOUNT_NAME, account);
   1148                     values.put(Groups.ACCOUNT_TYPE, DEFAULT_ACCOUNT_TYPE);
   1149                     values.put(Groups.GROUP_VISIBLE, true);
   1150                     values.put(Groups.SOURCE_ID, syncId);
   1151                     groupId = mTargetDb.insert(Tables.GROUPS, null, values);
   1152                 }
   1153             }
   1154         } else {
   1155             groupId = c.getLong(GroupMembershipQuery.GROUP_ID);
   1156         }
   1157 
   1158         insert.bindLong(GroupMembershipInsert.RAW_CONTACT_ID, personId);
   1159         insert.bindLong(GroupMembershipInsert.MIMETYPE_ID, mGroupMembershipMimetypeId);
   1160         insert.bindLong(GroupMembershipInsert.GROUP_ROW_ID, groupId);
   1161         insert(insert);
   1162     }
   1163 
   1164     private interface CallsQuery {
   1165         String TABLE = "calls";
   1166 
   1167         String[] COLUMNS = {
   1168                 "_id", "number", "date", "duration", "type", "new", "name", "numbertype",
   1169                 "numberlabel"
   1170         };
   1171 
   1172         static int ID = 0;
   1173         static int NUMBER = 1;
   1174         static int DATE = 2;
   1175         static int DURATION = 3;
   1176         static int TYPE = 4;
   1177         static int NEW = 5;
   1178         static int NAME = 6;
   1179         static int NUMBER_TYPE = 7;
   1180         static int NUMBER_LABEL = 8;
   1181     }
   1182 
   1183     private void importCalls() {
   1184         Cursor c = mSourceDb.query(CallsQuery.TABLE, CallsQuery.COLUMNS, null, null,
   1185                 null, null, null);
   1186         try {
   1187             while (c.moveToNext()) {
   1188                 insertCall(c);
   1189             }
   1190         } finally {
   1191             c.close();
   1192         }
   1193     }
   1194 
   1195     private void insertCall(Cursor c) {
   1196 
   1197         // Cannot use batch operations here, because call log is serviced by a separate provider
   1198         mValues.clear();
   1199         mValues.put(Calls._ID, c.getLong(CallsQuery.ID));
   1200         mValues.put(Calls.NUMBER, c.getString(CallsQuery.NUMBER));
   1201         mValues.put(Calls.DATE, c.getLong(CallsQuery.DATE));
   1202         mValues.put(Calls.DURATION, c.getLong(CallsQuery.DURATION));
   1203         mValues.put(Calls.NEW, c.getLong(CallsQuery.NEW));
   1204         mValues.put(Calls.TYPE, c.getLong(CallsQuery.TYPE));
   1205         mValues.put(Calls.CACHED_NAME, c.getString(CallsQuery.NAME));
   1206         mValues.put(Calls.CACHED_NUMBER_LABEL, c.getString(CallsQuery.NUMBER_LABEL));
   1207         mValues.put(Calls.CACHED_NUMBER_TYPE, c.getString(CallsQuery.NUMBER_TYPE));
   1208 
   1209         // TODO: confirm that we can use the CallLogProvider at this point, that it is guaranteed
   1210         // to have been registered.
   1211         mResolver.insert(Calls.CONTENT_URI, mValues);
   1212     }
   1213 
   1214     private void updateDisplayNamesAndLookupKeys() {
   1215         // Compute display names, sort keys, lookup key, etc. for all Raw Cont
   1216         Cursor cursor = mResolver.query(RawContacts.CONTENT_URI,
   1217                 new String[] { RawContacts._ID }, null, null, null);
   1218         try {
   1219             while (cursor.moveToNext()) {
   1220                 long rawContactId = cursor.getLong(0);
   1221                 mDbHelper.updateRawContactDisplayName(mTargetDb, rawContactId);
   1222                 mContactsProvider.updateLookupKeyForRawContact(mTargetDb, rawContactId);
   1223             }
   1224         } finally {
   1225             cursor.close();
   1226         }
   1227     }
   1228 
   1229     private interface DeletedPeopleQuery {
   1230         String TABLE = "_deleted_people";
   1231 
   1232         String[] COLUMNS = {
   1233                 "_sync_id", "_sync_account"
   1234         };
   1235 
   1236         static int _SYNC_ID = 0;
   1237         static int _SYNC_ACCOUNT = 1;
   1238     }
   1239 
   1240     private interface DeletedRawContactInsert {
   1241         String INSERT_SQL = "INSERT INTO " + Tables.RAW_CONTACTS + "(" +
   1242                 RawContacts.ACCOUNT_NAME + "," +
   1243                 RawContacts.ACCOUNT_TYPE + "," +
   1244                 RawContacts.SOURCE_ID + "," +
   1245                 RawContacts.DELETED + "," +
   1246                 RawContacts.AGGREGATION_MODE +
   1247          ") VALUES (?,?,?,?,?)";
   1248 
   1249 
   1250         int ACCOUNT_NAME = 1;
   1251         int ACCOUNT_TYPE = 2;
   1252         int SOURCE_ID = 3;
   1253         int DELETED = 4;
   1254         int AGGREGATION_MODE = 5;
   1255     }
   1256 
   1257     private void importDeletedPeople() {
   1258         SQLiteStatement insert = mTargetDb.compileStatement(DeletedRawContactInsert.INSERT_SQL);
   1259         Cursor c = mSourceDb.query(DeletedPeopleQuery.TABLE, DeletedPeopleQuery.COLUMNS, null, null,
   1260                 null, null, null);
   1261         try {
   1262             while (c.moveToNext()) {
   1263                 insertDeletedPerson(c, insert);
   1264             }
   1265         } finally {
   1266             c.close();
   1267             insert.close();
   1268         }
   1269     }
   1270 
   1271     private void insertDeletedPerson(Cursor c, SQLiteStatement insert) {
   1272         String account = c.getString(DeletedPeopleQuery._SYNC_ACCOUNT);
   1273         if (account == null) {
   1274             return;
   1275         }
   1276 
   1277         insert.bindString(DeletedRawContactInsert.ACCOUNT_NAME, account);
   1278         insert.bindString(DeletedRawContactInsert.ACCOUNT_TYPE, DEFAULT_ACCOUNT_TYPE);
   1279         bindString(insert, DeletedRawContactInsert.SOURCE_ID,
   1280                 c.getString(DeletedPeopleQuery._SYNC_ID));
   1281         insert.bindLong(DeletedRawContactInsert.DELETED, 1);
   1282         insert.bindLong(DeletedRawContactInsert.AGGREGATION_MODE,
   1283                 RawContacts.AGGREGATION_MODE_DISABLED);
   1284         insert(insert);
   1285     }
   1286 
   1287     private void bindString(SQLiteStatement insert, int index, String string) {
   1288         if (string == null) {
   1289             insert.bindNull(index);
   1290         } else {
   1291             insert.bindString(index, string);
   1292         }
   1293     }
   1294 
   1295     private long insert(SQLiteStatement insertStatement) {
   1296         long rowId = insertStatement.executeInsert();
   1297         if (rowId == 0) {
   1298             throw new RuntimeException("Insert failed");
   1299         }
   1300 
   1301         mBatchCounter++;
   1302         if (mBatchCounter >= INSERT_BATCH_SIZE) {
   1303             mTargetDb.setTransactionSuccessful();
   1304             mTargetDb.endTransaction();
   1305             mTargetDb.beginTransaction();
   1306             mBatchCounter = 0;
   1307         }
   1308         return rowId;
   1309     }
   1310 }
   1311