• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
LegacyContactImporter(Context context, ContactsProvider2 contactsProvider)104     public LegacyContactImporter(Context context, ContactsProvider2 contactsProvider) {
105         mContext = context;
106         mContactsProvider = contactsProvider;
107         mResolver = mContactsProvider.getContext().getContentResolver();
108         mNameLookupBuilder = mContactsProvider.getNameLookupBuilder();
109     }
110 
importContacts()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 
getEstimatedStorageRequirement()153     public long getEstimatedStorageRequirement() {
154         return mEstimatedStorageRequirement;
155     }
156 
importContactsFromLegacyDb()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      */
checkForImportFailureTest()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 
importGroups()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 
insertGroup(Cursor c, SQLiteStatement insert)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 
importPeople()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 
insertRawContact(Cursor c, SQLiteStatement insert)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 
insertContact(Cursor c, SQLiteStatement insert)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 
insertStructuredName(Cursor c, SQLiteStatement insert)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 
insertNote(Cursor c, SQLiteStatement insert)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 
importOrganizations()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 
insertOrganization(Cursor c, SQLiteStatement insert)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 
importContactMethods()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 
insertEmail(Cursor c, SQLiteStatement insert)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 
insertIm(Cursor c, SQLiteStatement insert)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 
insertPostal(Cursor c, SQLiteStatement insert)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 
importPhones()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 
insertPhone(Cursor c, SQLiteStatement phoneInsert, SQLiteStatement phoneLookupInsert, SQLiteStatement hasPhoneUpdate)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 
importPhotos()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 
insertPhoto(Cursor c, SQLiteStatement insert, SQLiteStatement photoIdUpdate)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 
importGroupMemberships()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 
insertGroupMembership(Cursor c, SQLiteStatement insert)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 
importCalls()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 
insertCall(Cursor c)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 
updateDisplayNamesAndLookupKeys()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 
importDeletedPeople()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 
insertDeletedPerson(Cursor c, SQLiteStatement insert)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 
bindString(SQLiteStatement insert, int index, String string)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 
insert(SQLiteStatement insertStatement)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