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