diff options
-rw-r--r-- | Android.mk | 13 | ||||
-rw-r--r-- | AndroidManifest.xml | 23 | ||||
-rw-r--r-- | MODULE_LICENSE_APACHE2 | 0 | ||||
-rw-r--r-- | NOTICE | 190 | ||||
-rw-r--r-- | res/drawable/app_icon.png | bin | 2995 -> 0 bytes | |||
-rw-r--r-- | res/values/strings.xml | 26 | ||||
-rw-r--r-- | src/com/android/providers/contacts/ContactsProvider.java | 4083 |
7 files changed, 0 insertions, 4335 deletions
diff --git a/Android.mk b/Android.mk deleted file mode 100644 index 8b34359f..00000000 --- a/Android.mk +++ /dev/null @@ -1,13 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := user development - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -LOCAL_JAVA_LIBRARIES := ext - -LOCAL_PACKAGE_NAME := ContactsProvider -LOCAL_CERTIFICATE := shared - -include $(BUILD_PACKAGE) diff --git a/AndroidManifest.xml b/AndroidManifest.xml deleted file mode 100644 index 0194108d..00000000 --- a/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.providers.contacts" - android:sharedUserId="android.uid.shared"> - - <uses-permission android:name="android.permission.READ_CONTACTS" /> - <uses-permission android:name="android.permission.WRITE_CONTACTS" /> - <uses-permission android:name="android.permission.GET_ACCOUNTS" /> - <uses-permission android:name="android.permission.READ_SYNC_STATS" /> - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" /> - <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.cp" /> - <uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_READ" /> - <uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_WRITE" /> - - <application android:process="android.process.acore" - android:label="@string/app_label" - android:icon="@drawable/app_icon"> - <provider android:name="ContactsProvider" android:authorities="contacts;call_log" - android:syncable="false" android:multiprocess="false" - android:readPermission="android.permission.READ_CONTACTS" - android:writePermission="android.permission.WRITE_CONTACTS" /> - </application> -</manifest> diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 deleted file mode 100644 index e69de29b..00000000 --- a/MODULE_LICENSE_APACHE2 +++ /dev/null diff --git a/NOTICE b/NOTICE deleted file mode 100644 index c5b1efa7..00000000 --- a/NOTICE +++ /dev/null @@ -1,190 +0,0 @@ - - Copyright (c) 2005-2008, The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - diff --git a/res/drawable/app_icon.png b/res/drawable/app_icon.png Binary files differdeleted file mode 100644 index 826656ff..00000000 --- a/res/drawable/app_icon.png +++ /dev/null diff --git a/res/values/strings.xml b/res/values/strings.xml deleted file mode 100644 index 799a73c3..00000000 --- a/res/values/strings.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2008 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - - <!-- This is the label for the application that stores contacts data --> - <string name="app_label">Contacts Storage</string> - - <!-- Strings for search suggestions --> - <string name="dialNumber">Dial number</string> - <string name="createNewContact">New contact</string> - <string name="usingNumber">Using <xliff:g id="number">%s</xliff:g></string> -</resources> diff --git a/src/com/android/providers/contacts/ContactsProvider.java b/src/com/android/providers/contacts/ContactsProvider.java deleted file mode 100644 index a825006e..00000000 --- a/src/com/android/providers/contacts/ContactsProvider.java +++ /dev/null @@ -1,4083 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.providers.contacts; - -import android.app.SearchManager; -import android.content.AbstractTableMerger; -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SyncableContentProvider; -import android.content.UriMatcher; -import android.content.AbstractSyncableContentProvider; -import android.database.Cursor; -import android.database.CursorJoiner; -import android.database.DatabaseUtils; -import android.database.SQLException; -import android.database.sqlite.SQLiteCursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteDoneException; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteQueryBuilder; -import android.database.sqlite.SQLiteStatement; -import android.net.Uri; -import android.os.Bundle; -import android.os.ParcelFileDescriptor; -import android.provider.CallLog; -import android.provider.CallLog.Calls; -import android.provider.Contacts; -import android.provider.Contacts.*; -import android.provider.LiveFolders; -import android.provider.SyncConstValue; -import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; -import android.util.Config; -import android.util.Log; -import com.android.internal.database.ArrayListCursor; -import com.google.android.collect.Maps; -import com.google.android.collect.Sets; - -import java.io.FileNotFoundException; -import java.util.*; - -public class ContactsProvider extends AbstractSyncableContentProvider { - private static final String STREQUENT_ORDER_BY = "times_contacted DESC, display_name ASC"; - private static final String STREQUENT_LIMIT = - "(SELECT COUNT(*) FROM people WHERE starred = 1) + 25"; - - private static final String PEOPLE_PHONES_JOIN = - "people LEFT OUTER JOIN phones ON people.primary_phone=phones._id " - + "LEFT OUTER JOIN presence ON (presence." + Presence.PERSON_ID + "=people._id)"; - - private static final String GTALK_PROTOCOL_STRING = - ContactMethods.encodePredefinedImProtocol(ContactMethods.PROTOCOL_GOOGLE_TALK); - - private static final String[] ID_TYPE_PROJECTION = new String[]{"_id", "type"}; - - private static final String[] sIsPrimaryProjectionWithoutKind = - new String[]{"isprimary", "person", "_id"}; - private static final String[] sIsPrimaryProjectionWithKind = - new String[]{"isprimary", "person", "_id", "kind"}; - - private static final String WHERE_ID = "_id=?"; - - private static final String sGroupsJoinString; - - private static final String PREFS_NAME_OWNER = "owner-info"; - private static final String PREF_OWNER_ID = "owner-id"; - - /** this is suitable for use by insert/update/delete/query and may be passed - * as a method call parameter. Only insert/update/delete/query should call .clear() on it */ - private final ContentValues mValues = new ContentValues(); - - /** this is suitable for local use in methods and should never be passed as a parameter to - * other methods (other than the DB layer) */ - private final ContentValues mValuesLocal = new ContentValues(); - - private String[] mAccounts = new String[0]; - private final Object mAccountsLock = new Object(); - - private DatabaseUtils.InsertHelper mDeletedPeopleInserter; - private DatabaseUtils.InsertHelper mPeopleInserter; - private int mIndexPeopleSyncId; - private int mIndexPeopleSyncTime; - private int mIndexPeopleSyncVersion; - private int mIndexPeopleSyncDirty; - private int mIndexPeopleSyncAccount; - private int mIndexPeopleName; - private int mIndexPeoplePhoneticName; - private int mIndexPeopleNotes; - private DatabaseUtils.InsertHelper mGroupsInserter; - private DatabaseUtils.InsertHelper mPhotosInserter; - private int mIndexPhotosPersonId; - private int mIndexPhotosSyncId; - private int mIndexPhotosSyncTime; - private int mIndexPhotosSyncVersion; - private int mIndexPhotosSyncDirty; - private int mIndexPhotosSyncAccount; - private int mIndexPhotosExistsOnServer; - private int mIndexPhotosSyncError; - private DatabaseUtils.InsertHelper mContactMethodsInserter; - private int mIndexContactMethodsPersonId; - private int mIndexContactMethodsLabel; - private int mIndexContactMethodsKind; - private int mIndexContactMethodsType; - private int mIndexContactMethodsData; - private int mIndexContactMethodsAuxData; - private int mIndexContactMethodsIsPrimary; - private DatabaseUtils.InsertHelper mOrganizationsInserter; - private int mIndexOrganizationsPersonId; - private int mIndexOrganizationsLabel; - private int mIndexOrganizationsType; - private int mIndexOrganizationsCompany; - private int mIndexOrganizationsTitle; - private int mIndexOrganizationsIsPrimary; - private DatabaseUtils.InsertHelper mExtensionsInserter; - private int mIndexExtensionsPersonId; - private int mIndexExtensionsName; - private int mIndexExtensionsValue; - private DatabaseUtils.InsertHelper mGroupMembershipInserter; - private int mIndexGroupMembershipPersonId; - private int mIndexGroupMembershipGroupSyncAccount; - private int mIndexGroupMembershipGroupSyncId; - private DatabaseUtils.InsertHelper mCallsInserter; - private DatabaseUtils.InsertHelper mPhonesInserter; - private int mIndexPhonesPersonId; - private int mIndexPhonesLabel; - private int mIndexPhonesType; - private int mIndexPhonesNumber; - private int mIndexPhonesNumberKey; - private int mIndexPhonesIsPrimary; - - public ContactsProvider() { - super(DATABASE_NAME, DATABASE_VERSION, Contacts.CONTENT_URI); - } - - @Override - protected void onDatabaseOpened(SQLiteDatabase db) { - maybeCreatePresenceTable(db); - - // Mark all the tables as syncable - db.markTableSyncable(sPeopleTable, sDeletedPeopleTable); - db.markTableSyncable(sPhonesTable, Phones.PERSON_ID, sPeopleTable); - db.markTableSyncable(sContactMethodsTable, ContactMethods.PERSON_ID, sPeopleTable); - db.markTableSyncable(sOrganizationsTable, Organizations.PERSON_ID, sPeopleTable); - db.markTableSyncable(sGroupmembershipTable, GroupMembership.PERSON_ID, sPeopleTable); - db.markTableSyncable(sExtensionsTable, Extensions.PERSON_ID, sPeopleTable); - db.markTableSyncable(sGroupsTable, sDeletedGroupsTable); - - mDeletedPeopleInserter = new DatabaseUtils.InsertHelper(db, sDeletedPeopleTable); - mPeopleInserter = new DatabaseUtils.InsertHelper(db, sPeopleTable); - mIndexPeopleSyncId = mPeopleInserter.getColumnIndex(People._SYNC_ID); - mIndexPeopleSyncTime = mPeopleInserter.getColumnIndex(People._SYNC_TIME); - mIndexPeopleSyncVersion = mPeopleInserter.getColumnIndex(People._SYNC_VERSION); - mIndexPeopleSyncDirty = mPeopleInserter.getColumnIndex(People._SYNC_DIRTY); - mIndexPeopleSyncAccount = mPeopleInserter.getColumnIndex(People._SYNC_ACCOUNT); - mIndexPeopleName = mPeopleInserter.getColumnIndex(People.NAME); - mIndexPeoplePhoneticName = mPeopleInserter.getColumnIndex(People.PHONETIC_NAME); - mIndexPeopleNotes = mPeopleInserter.getColumnIndex(People.NOTES); - - mGroupsInserter = new DatabaseUtils.InsertHelper(db, sGroupsTable); - - mPhotosInserter = new DatabaseUtils.InsertHelper(db, sPhotosTable); - mIndexPhotosPersonId = mPhotosInserter.getColumnIndex(Photos.PERSON_ID); - mIndexPhotosSyncId = mPhotosInserter.getColumnIndex(Photos._SYNC_ID); - mIndexPhotosSyncTime = mPhotosInserter.getColumnIndex(Photos._SYNC_TIME); - mIndexPhotosSyncVersion = mPhotosInserter.getColumnIndex(Photos._SYNC_VERSION); - mIndexPhotosSyncDirty = mPhotosInserter.getColumnIndex(Photos._SYNC_DIRTY); - mIndexPhotosSyncAccount = mPhotosInserter.getColumnIndex(Photos._SYNC_ACCOUNT); - mIndexPhotosSyncError = mPhotosInserter.getColumnIndex(Photos.SYNC_ERROR); - mIndexPhotosExistsOnServer = mPhotosInserter.getColumnIndex(Photos.EXISTS_ON_SERVER); - - mContactMethodsInserter = new DatabaseUtils.InsertHelper(db, sContactMethodsTable); - mIndexContactMethodsPersonId = mContactMethodsInserter.getColumnIndex(ContactMethods.PERSON_ID); - mIndexContactMethodsLabel = mContactMethodsInserter.getColumnIndex(ContactMethods.LABEL); - mIndexContactMethodsKind = mContactMethodsInserter.getColumnIndex(ContactMethods.KIND); - mIndexContactMethodsType = mContactMethodsInserter.getColumnIndex(ContactMethods.TYPE); - mIndexContactMethodsData = mContactMethodsInserter.getColumnIndex(ContactMethods.DATA); - mIndexContactMethodsAuxData = mContactMethodsInserter.getColumnIndex(ContactMethods.AUX_DATA); - mIndexContactMethodsIsPrimary = mContactMethodsInserter.getColumnIndex(ContactMethods.ISPRIMARY); - - mOrganizationsInserter = new DatabaseUtils.InsertHelper(db, sOrganizationsTable); - mIndexOrganizationsPersonId = mOrganizationsInserter.getColumnIndex(Organizations.PERSON_ID); - mIndexOrganizationsLabel = mOrganizationsInserter.getColumnIndex(Organizations.LABEL); - mIndexOrganizationsType = mOrganizationsInserter.getColumnIndex(Organizations.TYPE); - mIndexOrganizationsCompany = mOrganizationsInserter.getColumnIndex(Organizations.COMPANY); - mIndexOrganizationsTitle = mOrganizationsInserter.getColumnIndex(Organizations.TITLE); - mIndexOrganizationsIsPrimary = mOrganizationsInserter.getColumnIndex(Organizations.ISPRIMARY); - - mExtensionsInserter = new DatabaseUtils.InsertHelper(db, sExtensionsTable); - mIndexExtensionsPersonId = mExtensionsInserter.getColumnIndex(Extensions.PERSON_ID); - mIndexExtensionsName = mExtensionsInserter.getColumnIndex(Extensions.NAME); - mIndexExtensionsValue = mExtensionsInserter.getColumnIndex(Extensions.VALUE); - - mGroupMembershipInserter = new DatabaseUtils.InsertHelper(db, sGroupmembershipTable); - mIndexGroupMembershipPersonId = mGroupMembershipInserter.getColumnIndex(GroupMembership.PERSON_ID); - mIndexGroupMembershipGroupSyncAccount = mGroupMembershipInserter.getColumnIndex(GroupMembership.GROUP_SYNC_ACCOUNT); - mIndexGroupMembershipGroupSyncId = mGroupMembershipInserter.getColumnIndex(GroupMembership.GROUP_SYNC_ID); - - mCallsInserter = new DatabaseUtils.InsertHelper(db, sCallsTable); - - mPhonesInserter = new DatabaseUtils.InsertHelper(db, sPhonesTable); - mIndexPhonesPersonId = mPhonesInserter.getColumnIndex(Phones.PERSON_ID); - mIndexPhonesLabel = mPhonesInserter.getColumnIndex(Phones.LABEL); - mIndexPhonesType = mPhonesInserter.getColumnIndex(Phones.TYPE); - mIndexPhonesNumber = mPhonesInserter.getColumnIndex(Phones.NUMBER); - mIndexPhonesNumberKey = mPhonesInserter.getColumnIndex(Phones.NUMBER_KEY); - mIndexPhonesIsPrimary = mPhonesInserter.getColumnIndex(Phones.ISPRIMARY); - } - - @Override - protected boolean upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion) { - boolean upgradeWasLossless = true; - if (oldVersion < 71) { - Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + - newVersion + ", which will destroy all old data"); - dropTables(db); - bootstrapDatabase(db); - return false; // this was lossy - } - if (oldVersion == 71) { - Log.i(TAG, "Upgrading contacts database from version " + oldVersion + " to " + - newVersion + ", which will preserve existing data"); - - db.delete("_sync_state", null, null); - mValuesLocal.clear(); - mValuesLocal.putNull(Photos._SYNC_VERSION); - mValuesLocal.putNull(Photos._SYNC_TIME); - db.update(sPhotosTable, mValuesLocal, null, null); - getContext().getContentResolver().startSync(Contacts.CONTENT_URI, new Bundle()); - oldVersion = 72; - } - if (oldVersion == 72) { - Log.i(TAG, "Upgrading contacts database from version " + oldVersion + " to " + - newVersion + ", which will preserve existing data"); - - // use new token format from 73 - db.execSQL("delete from peopleLookup"); - try { - DatabaseUtils.longForQuery(db, - "SELECT _TOKENIZE('peopleLookup', _id, name, ' ') from people;", - null); - } catch (SQLiteDoneException ex) { - // it is ok to throw this, - // it just means you don't have data in people table - } - oldVersion = 73; - } - // There was a bug for a while in the upgrade logic where going from 72 to 74 would skip - // the step from 73 to 74, so 74 to 75 just tries the same steps, and gracefully handles - // errors in case the device was started freshly at 74. - if (oldVersion == 73 || oldVersion == 74) { - Log.i(TAG, "Upgrading contacts database from version " + oldVersion + " to " + - newVersion + ", which will preserve existing data"); - - try { - db.execSQL("ALTER TABLE calls ADD name TEXT;"); - db.execSQL("ALTER TABLE calls ADD numbertype INTEGER;"); - db.execSQL("ALTER TABLE calls ADD numberlabel TEXT;"); - } catch (SQLiteException sqle) { - // Maybe the table was altered already... Shouldn't be an issue. - } - oldVersion = 75; - } - // There were some indices added in version 76 - if (oldVersion == 75) { - Log.i(TAG, "Upgrading contacts database from version " + oldVersion + " to " + - newVersion + ", which will preserve existing data"); - - // add the new indices - db.execSQL("CREATE INDEX IF NOT EXISTS groupsSyncDirtyIndex" - + " ON groups (" + Groups._SYNC_DIRTY + ");"); - db.execSQL("CREATE INDEX IF NOT EXISTS photosSyncDirtyIndex" - + " ON photos (" + Photos._SYNC_DIRTY + ");"); - db.execSQL("CREATE INDEX IF NOT EXISTS peopleSyncDirtyIndex" - + " ON people (" + People._SYNC_DIRTY + ");"); - oldVersion = 76; - } - - if (oldVersion == 76 || oldVersion == 77) { - db.execSQL("DELETE FROM people"); - db.execSQL("DELETE FROM groups"); - db.execSQL("DELETE FROM photos"); - db.execSQL("DELETE FROM _deleted_people"); - db.execSQL("DELETE FROM _deleted_groups"); - upgradeWasLossless = false; - oldVersion = 78; - } - - if (oldVersion == 78) { - db.execSQL("UPDATE photos SET _sync_dirty=0 where _sync_dirty is null;"); - oldVersion = 79; - } - - if (oldVersion == 79) { - try { - db.execSQL("ALTER TABLE people ADD phonetic_name TEXT COLLATE LOCALIZED;"); - } catch (SQLiteException sqle) { - // Maybe the table was altered already... Shouldn't be an issue. - } - oldVersion = 80; - } - - return upgradeWasLossless; - } - - protected void dropTables(SQLiteDatabase db) { - db.execSQL("DROP TABLE IF EXISTS people"); - db.execSQL("DROP TABLE IF EXISTS peopleLookup"); - db.execSQL("DROP TABLE IF EXISTS _deleted_people"); - db.execSQL("DROP TABLE IF EXISTS phones"); - db.execSQL("DROP TABLE IF EXISTS contact_methods"); - db.execSQL("DROP TABLE IF EXISTS calls"); - db.execSQL("DROP TABLE IF EXISTS organizations"); - db.execSQL("DROP TABLE IF EXISTS voice_dialer_timestamp"); - db.execSQL("DROP TABLE IF EXISTS groups"); - db.execSQL("DROP TABLE IF EXISTS _deleted_groups"); - db.execSQL("DROP TABLE IF EXISTS groupmembership"); - db.execSQL("DROP TABLE IF EXISTS photos"); - db.execSQL("DROP TABLE IF EXISTS extensions"); - db.execSQL("DROP TABLE IF EXISTS settings"); - } - - @Override - protected void bootstrapDatabase(SQLiteDatabase db) { - super.bootstrapDatabase(db); - db.execSQL("CREATE TABLE people (" + - People._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + - People._SYNC_ACCOUNT + " TEXT," + // From the sync source - People._SYNC_ID + " TEXT," + // From the sync source - People._SYNC_TIME + " TEXT," + // From the sync source - People._SYNC_VERSION + " TEXT," + // From the sync source - People._SYNC_LOCAL_ID + " INTEGER," + // Used while syncing, not persistent - People._SYNC_DIRTY + " INTEGER NOT NULL DEFAULT 0," + - // if syncable, non-zero if the record - // has local, unsynced, changes - People._SYNC_MARK + " INTEGER," + // Used to filter out new rows - - People.NAME + " TEXT COLLATE LOCALIZED," + - People.NOTES + " TEXT COLLATE LOCALIZED," + - People.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," + - People.LAST_TIME_CONTACTED + " INTEGER," + - People.STARRED + " INTEGER NOT NULL DEFAULT 0," + - People.PRIMARY_PHONE_ID + " INTEGER REFERENCES phones(_id)," + - People.PRIMARY_ORGANIZATION_ID + " INTEGER REFERENCES organizations(_id)," + - People.PRIMARY_EMAIL_ID + " INTEGER REFERENCES contact_methods(_id)," + - People.PHOTO_VERSION + " TEXT," + - People.CUSTOM_RINGTONE + " TEXT," + - People.SEND_TO_VOICEMAIL + " INTEGER," + - People.PHONETIC_NAME + " TEXT COLLATE LOCALIZED" + - ");"); - - db.execSQL("CREATE INDEX peopleNameIndex ON people (" + People.NAME + ");"); - db.execSQL("CREATE INDEX peopleSyncDirtyIndex ON people (" + People._SYNC_DIRTY + ");"); - db.execSQL("CREATE INDEX peopleSyncIdIndex ON people (" + People._SYNC_ID + ");"); - - db.execSQL("CREATE TRIGGER people_timesContacted UPDATE OF last_time_contacted ON people " + - "BEGIN " + - "UPDATE people SET " - + People.TIMES_CONTACTED + " = (new." + People.TIMES_CONTACTED + " + 1)" - + " WHERE _id = new._id;" + - "END"); - - // table of all the groups that exist for an account - db.execSQL("CREATE TABLE groups (" + - Groups._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + - Groups._SYNC_ACCOUNT + " TEXT," + // From the sync source - Groups._SYNC_ID + " TEXT," + // From the sync source - Groups._SYNC_TIME + " TEXT," + // From the sync source - Groups._SYNC_VERSION + " TEXT," + // From the sync source - Groups._SYNC_LOCAL_ID + " INTEGER," + // Used while syncing, not persistent - Groups._SYNC_DIRTY + " INTEGER NOT NULL DEFAULT 0," + - // if syncable, non-zero if the record - // has local, unsynced, changes - Groups._SYNC_MARK + " INTEGER," + // Used to filter out new rows - - Groups.NAME + " TEXT NOT NULL," + - Groups.NOTES + " TEXT," + - Groups.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 0," + - Groups.SYSTEM_ID + " TEXT," + - "UNIQUE(" + - Groups.NAME + "," + Groups.SYSTEM_ID + "," + Groups._SYNC_ACCOUNT + ")" + - ");"); - - db.execSQL("CREATE INDEX groupsSyncDirtyIndex ON groups (" + Groups._SYNC_DIRTY + ");"); - - if (!isTemporary()) { - // Add the system groups, since we always need them. - db.execSQL("INSERT INTO groups (" + Groups.NAME + ", " + Groups.SYSTEM_ID + ") VALUES " - + "('" + Groups.GROUP_MY_CONTACTS + "', '" + Groups.GROUP_MY_CONTACTS + "')"); - } - - db.execSQL("CREATE TABLE peopleLookup (" + - "token TEXT," + - "source INTEGER REFERENCES people(_id)" + - ");"); - db.execSQL("CREATE INDEX peopleLookupIndex ON peopleLookup (" + - "token," + - "source" + - ");"); - - db.execSQL("CREATE TABLE photos (" - + Photos._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + Photos.EXISTS_ON_SERVER + " INTEGER NOT NULL DEFAULT 0," - + Photos.PERSON_ID + " INTEGER REFERENCES people(_id), " - + Photos.LOCAL_VERSION + " TEXT," - + Photos.DATA + " BLOB," - + Photos.SYNC_ERROR + " TEXT," - + Photos._SYNC_ACCOUNT + " TEXT," - + Photos._SYNC_ID + " TEXT," - + Photos._SYNC_TIME + " TEXT," - + Photos._SYNC_VERSION + " TEXT," - + Photos._SYNC_LOCAL_ID + " INTEGER," - + Photos._SYNC_DIRTY + " INTEGER NOT NULL DEFAULT 0," - + Photos._SYNC_MARK + " INTEGER," - + "UNIQUE(" + Photos.PERSON_ID + ") " - + ")"); - - db.execSQL("CREATE INDEX photosSyncDirtyIndex ON photos (" + Photos._SYNC_DIRTY + ");"); - db.execSQL("CREATE INDEX photoPersonIndex ON photos (person);"); - - // Delete the photo row when the people row is deleted - db.execSQL("" - + " CREATE TRIGGER peopleDeleteAndPhotos DELETE ON people " - + " BEGIN" - + " DELETE FROM photos WHERE person=OLD._id;" - + " END"); - - db.execSQL("CREATE TABLE _deleted_people (" + - "_sync_version TEXT," + // From the sync source - "_sync_id TEXT," + - (isTemporary() ? "_sync_local_id INTEGER," : "") + // Used while syncing, - "_sync_account TEXT," + - "_sync_mark INTEGER)"); // Used to filter out new rows - - db.execSQL("CREATE TABLE _deleted_groups (" + - "_sync_version TEXT," + // From the sync source - "_sync_id TEXT," + - (isTemporary() ? "_sync_local_id INTEGER," : "") + // Used while syncing, - "_sync_account TEXT," + - "_sync_mark INTEGER)"); // Used to filter out new rows - - db.execSQL("CREATE TABLE phones (" + - "_id INTEGER PRIMARY KEY AUTOINCREMENT," + - "person INTEGER REFERENCES people(_id)," + - "type INTEGER NOT NULL," + // kind specific (home, work, etc) - "number TEXT," + - "number_key TEXT," + - "label TEXT," + - "isprimary INTEGER NOT NULL DEFAULT 0" + - ");"); - db.execSQL("CREATE INDEX phonesIndex1 ON phones (person);"); - db.execSQL("CREATE INDEX phonesIndex2 ON phones (number_key);"); - - db.execSQL("CREATE TABLE contact_methods (" + - "_id INTEGER PRIMARY KEY AUTOINCREMENT," + - "person INTEGER REFERENCES people(_id)," + - "kind INTEGER NOT NULL," + // the kind of contact method - "data TEXT," + - "aux_data TEXT," + - "type INTEGER NOT NULL," + // kind specific (home, work, etc) - "label TEXT," + - "isprimary INTEGER NOT NULL DEFAULT 0" + - ");"); - db.execSQL("CREATE INDEX contactMethodsPeopleIndex " - + "ON contact_methods (person);"); - - // The table for recent calls is here so we can do table joins - // on people, phones, and calls all in one place. - db.execSQL("CREATE TABLE calls (" + - "_id INTEGER PRIMARY KEY AUTOINCREMENT," + - "number TEXT," + - "date INTEGER," + - "duration INTEGER," + - "type INTEGER," + - "new INTEGER," + - "name TEXT," + - "numbertype INTEGER," + - "numberlabel TEXT" + - ");"); - - // Various settings for the contacts sync adapter. The _sync_account column may - // be null, but it must not be the empty string. - db.execSQL("CREATE TABLE settings (" + - "_id INTEGER PRIMARY KEY," + - "_sync_account TEXT," + - "key STRING NOT NULL," + - "value STRING " + - ");"); - - // The table for the organizations of a person. - db.execSQL("CREATE TABLE organizations (" + - "_id INTEGER PRIMARY KEY AUTOINCREMENT," + - "company TEXT," + - "title TEXT," + - "isprimary INTEGER NOT NULL DEFAULT 0," + - "type INTEGER NOT NULL," + // kind specific (home, work, etc) - "label TEXT," + - "person INTEGER REFERENCES people(_id)" + - ");"); - db.execSQL("CREATE INDEX organizationsIndex1 ON organizations (person);"); - - // The table for the extensions of a person. - db.execSQL("CREATE TABLE extensions (" + - "_id INTEGER PRIMARY KEY AUTOINCREMENT," + - "name TEXT NOT NULL," + - "value TEXT NOT NULL," + - "person INTEGER REFERENCES people(_id)," + - "UNIQUE(person, name)" + - ");"); - db.execSQL("CREATE INDEX extensionsIndex1 ON extensions (person, name);"); - - // The table for the groups of a person. - db.execSQL("CREATE TABLE groupmembership (" + - "_id INTEGER PRIMARY KEY," + - "person INTEGER REFERENCES people(_id)," + - "group_id INTEGER REFERENCES groups(_id)," + - "group_sync_account STRING," + - "group_sync_id STRING" + - ");"); - db.execSQL("CREATE INDEX groupmembershipIndex1 ON groupmembership (person, group_id);"); - db.execSQL("CREATE INDEX groupmembershipIndex2 ON groupmembership (group_id, person);"); - db.execSQL("CREATE INDEX groupmembershipIndex3 ON groupmembership " - + "(group_sync_account, group_sync_id);"); - - // Trigger to completely remove a contacts data when they're deleted - db.execSQL("CREATE TRIGGER contact_cleanup DELETE ON people " + - "BEGIN " + - "DELETE FROM peopleLookup WHERE source = old._id;" + - "DELETE FROM phones WHERE person = old._id;" + - "DELETE FROM contact_methods WHERE person = old._id;" + - "DELETE FROM organizations WHERE person = old._id;" + - "DELETE FROM groupmembership WHERE person = old._id;" + - "DELETE FROM extensions WHERE person = old._id;" + - "END"); - - // Trigger to disassociate the groupmembership from the groups when an - // groups entry is deleted - db.execSQL("CREATE TRIGGER groups_cleanup DELETE ON groups " + - "BEGIN " + - "UPDATE groupmembership SET group_id = null WHERE group_id = old._id;" + - "END"); - - // Trigger to move an account_people row to _deleted_account_people when it is deleted - db.execSQL("CREATE TRIGGER groups_to_deleted DELETE ON groups " + - "WHEN old._sync_id is not null " + - "BEGIN " + - "INSERT INTO _deleted_groups " + - "(_sync_id, _sync_account, _sync_version) " + - "VALUES (old._sync_id, old._sync_account, " + - "old._sync_version);" + - "END"); - - // Triggers to keep the peopleLookup table up to date - db.execSQL("CREATE TRIGGER peopleLookup_update UPDATE OF name ON people " + - "BEGIN " + - "DELETE FROM peopleLookup WHERE source = new._id;" + - "SELECT _TOKENIZE('peopleLookup', new._id, new.name, ' ');" + - "END"); - db.execSQL("CREATE TRIGGER peopleLookup_insert AFTER INSERT ON people " + - "BEGIN " + - "SELECT _TOKENIZE('peopleLookup', new._id, new.name, ' ');" + - "END"); - - // Triggers to set the _sync_dirty flag when a phone is changed, - // inserted or deleted - db.execSQL("CREATE TRIGGER phones_update UPDATE ON phones " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" + - "END"); - db.execSQL("CREATE TRIGGER phones_insert INSERT ON phones " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=new.person;" + - "END"); - db.execSQL("CREATE TRIGGER phones_delete DELETE ON phones " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" + - "END"); - - // Triggers to set the _sync_dirty flag when a contact_method is - // changed, inserted or deleted - db.execSQL("CREATE TRIGGER contact_methods_update UPDATE ON contact_methods " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" + - "END"); - db.execSQL("CREATE TRIGGER contact_methods_insert INSERT ON contact_methods " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=new.person;" + - "END"); - db.execSQL("CREATE TRIGGER contact_methods_delete DELETE ON contact_methods " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" + - "END"); - - // Triggers for when an organization is changed, inserted or deleted - db.execSQL("CREATE TRIGGER organizations_update AFTER UPDATE ON organizations " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person; " + - "END"); - db.execSQL("CREATE TRIGGER organizations_insert INSERT ON organizations " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=new.person; " + - "END"); - db.execSQL("CREATE TRIGGER organizations_delete DELETE ON organizations " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" + - "END"); - - // Triggers for when an groupmembership is changed, inserted or deleted - db.execSQL("CREATE TRIGGER groupmembership_update AFTER UPDATE ON groupmembership " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person; " + - "END"); - db.execSQL("CREATE TRIGGER groupmembership_insert INSERT ON groupmembership " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=new.person; " + - "END"); - db.execSQL("CREATE TRIGGER groupmembership_delete DELETE ON groupmembership " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" + - "END"); - - // Triggers for when an extension is changed, inserted or deleted - db.execSQL("CREATE TRIGGER extensions_update AFTER UPDATE ON extensions " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person; " + - "END"); - db.execSQL("CREATE TRIGGER extensions_insert INSERT ON extensions " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=new.person; " + - "END"); - db.execSQL("CREATE TRIGGER extensions_delete DELETE ON extensions " + - "BEGIN " + - "UPDATE people SET _sync_dirty=1 WHERE people._id=old.person;" + - "END"); - - createTypeLabelTrigger(db, sPhonesTable, "INSERT"); - createTypeLabelTrigger(db, sPhonesTable, "UPDATE"); - createTypeLabelTrigger(db, sOrganizationsTable, "INSERT"); - createTypeLabelTrigger(db, sOrganizationsTable, "UPDATE"); - createTypeLabelTrigger(db, sContactMethodsTable, "INSERT"); - createTypeLabelTrigger(db, sContactMethodsTable, "UPDATE"); - - // Temporary table that holds a time stamp of the last time data the voice - // dialer is interested in has changed so the grammar won't need to be - // recompiled when unused data is changed. - db.execSQL("CREATE TABLE voice_dialer_timestamp (" + - "_id INTEGER PRIMARY KEY," + - "timestamp INTEGER" + - ");"); - db.execSQL("INSERT INTO voice_dialer_timestamp (_id, timestamp) VALUES " + - "(1, strftime('%s', 'now'));"); - db.execSQL("CREATE TRIGGER timestamp_trigger1 AFTER UPDATE ON phones " + - "BEGIN " + - "UPDATE voice_dialer_timestamp SET timestamp=strftime('%s', 'now') "+ - "WHERE _id=1;" + - "END"); - db.execSQL("CREATE TRIGGER timestamp_trigger2 AFTER UPDATE OF name ON people " + - "BEGIN " + - "UPDATE voice_dialer_timestamp SET timestamp=strftime('%s', 'now') " + - "WHERE _id=1;" + - "END"); - } - - private void createTypeLabelTrigger(SQLiteDatabase db, String table, String operation) { - final String name = table + "_" + operation + "_typeAndLabel"; - db.execSQL("CREATE TRIGGER " + name + " AFTER " + operation + " ON " + table - + " WHEN (NEW.type != 0 AND NEW.label IS NOT NULL) OR " - + " (NEW.type = 0 AND NEW.label IS NULL)" - + " BEGIN " - + " SELECT RAISE (ABORT, 'exactly one of type or label must be set'); " - + " END"); - } - - private void maybeCreatePresenceTable(SQLiteDatabase db) { - // Load the presence table from the presence_db. Just create the table - // if we are - String cpDbName; - if (!isTemporary()) { - db.execSQL("ATTACH DATABASE ':memory:' AS presence_db;"); - cpDbName = "presence_db."; - } else { - cpDbName = ""; - } - db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + "presence ("+ - Presence._ID + " INTEGER PRIMARY KEY," + - Presence.PERSON_ID + " INTEGER REFERENCES people(_id)," + - Presence.IM_PROTOCOL + " TEXT," + - Presence.IM_HANDLE + " TEXT," + - Presence.IM_ACCOUNT + " TEXT," + - Presence.PRESENCE_STATUS + " INTEGER," + - Presence.PRESENCE_CUSTOM_STATUS + " TEXT," + - "UNIQUE(" + Presence.IM_PROTOCOL + ", " + Presence.IM_HANDLE + ", " - + Presence.IM_ACCOUNT + ")" + - ");"); - - db.execSQL("CREATE INDEX IF NOT EXISTS " + cpDbName + "presenceIndex ON presence (" - + Presence.PERSON_ID + ");"); - } - - @SuppressWarnings("deprecation") - private String buildPeopleLookupWhereClause(String filterParam) { - StringBuilder filter = new StringBuilder( - "people._id IN (SELECT source FROM peopleLookup WHERE token GLOB "); - // NOTE: Query parameters won't work here since the SQL compiler - // needs to parse the actual string to know that it can use the - // index to do a prefix scan. - DatabaseUtils.appendEscapedSQLString(filter, - DatabaseUtils.getHexCollationKey(filterParam) + "*"); - filter.append(')'); - return filter.toString(); - } - - @Override - public Cursor queryInternal(Uri url, String[] projectionIn, - String selection, String[] selectionArgs, String sort) { - - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - Uri notificationUri = Contacts.CONTENT_URI; - - // Generate the body of the query - int match = sURIMatcher.match(url); - - if (Config.LOGV) Log.v(TAG, "ContactsProvider.query: url=" + url + ", match is " + match); - - switch (match) { - case DELETED_GROUPS: - if (!isTemporary()) { - throw new UnsupportedOperationException(); - } - - qb.setTables(sDeletedGroupsTable); - break; - - case GROUPS_ID: - qb.appendWhere("_id="); - qb.appendWhere(url.getPathSegments().get(1)); - // fall through - case GROUPS: - qb.setTables(sGroupsTable); - qb.setProjectionMap(sGroupsProjectionMap); - break; - - case SETTINGS: - qb.setTables(sSettingsTable); - break; - - case PEOPLE_GROUPMEMBERSHIP_ID: - qb.appendWhere("groupmembership._id="); - qb.appendWhere(url.getPathSegments().get(3)); - qb.appendWhere(" AND "); - // fall through - case PEOPLE_GROUPMEMBERSHIP: - qb.appendWhere(sGroupsJoinString + " AND "); - qb.appendWhere("person=" + url.getPathSegments().get(1)); - qb.setTables("groups, groupmembership"); - qb.setProjectionMap(sGroupMembershipProjectionMap); - break; - - case GROUPMEMBERSHIP_ID: - qb.appendWhere("groupmembership._id="); - qb.appendWhere(url.getPathSegments().get(1)); - qb.appendWhere(" AND "); - // fall through - case GROUPMEMBERSHIP: - qb.setTables("groups, groupmembership"); - qb.setProjectionMap(sGroupMembershipProjectionMap); - qb.appendWhere(sGroupsJoinString); - break; - - case GROUPMEMBERSHIP_RAW: - qb.setTables("groupmembership"); - break; - - case GROUP_NAME_MEMBERS_FILTER: - if (url.getPathSegments().size() > 5) { - qb.appendWhere(buildPeopleLookupWhereClause(url.getLastPathSegment())); - qb.appendWhere(" AND "); - } - // fall through - case GROUP_NAME_MEMBERS: - qb.setTables(PEOPLE_PHONES_JOIN); - qb.setProjectionMap(sPeopleProjectionMap); - qb.appendWhere("people._id IN (SELECT person FROM groupmembership JOIN groups " + - "ON (group_id=groups._id OR " + - "(group_sync_id = groups._sync_id AND " + - "group_sync_account = groups._sync_account)) "+ - "WHERE " + Groups.NAME + "=" - + DatabaseUtils.sqlEscapeString(url.getPathSegments().get(2)) + ")"); - break; - - case GROUP_SYSTEM_ID_MEMBERS_FILTER: - if (url.getPathSegments().size() > 5) { - qb.appendWhere(buildPeopleLookupWhereClause(url.getLastPathSegment())); - qb.appendWhere(" AND "); - } - // fall through - case GROUP_SYSTEM_ID_MEMBERS: - qb.setTables(PEOPLE_PHONES_JOIN); - qb.setProjectionMap(sPeopleProjectionMap); - qb.appendWhere("people._id IN (SELECT person FROM groupmembership JOIN groups " + - "ON (group_id=groups._id OR " + - "(group_sync_id = groups._sync_id AND " + - "group_sync_account = groups._sync_account)) "+ - "WHERE " + Groups.SYSTEM_ID + "=" - + DatabaseUtils.sqlEscapeString(url.getPathSegments().get(2)) + ")"); - break; - - case PEOPLE: - qb.setTables(PEOPLE_PHONES_JOIN); - qb.setProjectionMap(sPeopleProjectionMap); - break; - case PEOPLE_RAW: - qb.setTables(sPeopleTable); - break; - - case PEOPLE_OWNER: - return queryOwner(projectionIn); - - case PEOPLE_WITH_PHONES_FILTER: - - qb.appendWhere("number IS NOT NULL AND "); - - // Fall through. - - case PEOPLE_FILTER: { - qb.setTables(PEOPLE_PHONES_JOIN); - qb.setProjectionMap(sPeopleProjectionMap); - if (url.getPathSegments().size() > 2) { - qb.appendWhere(buildPeopleLookupWhereClause(url.getLastPathSegment())); - } - break; - } - - case PHOTOS_ID: - qb.appendWhere("_id="+url.getPathSegments().get(1)); - // Fall through. - case PHOTOS: - qb.setTables(sPhotosTable); - qb.setProjectionMap(sPhotosProjectionMap); - break; - - case PEOPLE_PHOTO: - qb.appendWhere("person="+url.getPathSegments().get(1)); - qb.setTables(sPhotosTable); - qb.setProjectionMap(sPhotosProjectionMap); - break; - - case SEARCH_SUGGESTIONS: { - // Force the default sort order, since the SearchManage doesn't ask for things - // sorted, though they should be - if (sort != null && !People.DEFAULT_SORT_ORDER.equals(sort)) { - throw new IllegalArgumentException("Sort ordering not allowed for this URI"); - } - sort = SearchManager.SUGGEST_COLUMN_TEXT_1 + " COLLATE LOCALIZED ASC"; - - // This will either setup the query builder so we can run the proper query below - // and return null, or it will return a cursor with the results already in it. - Cursor c = handleSearchSuggestionsQuery(url, qb); - if (c != null) { - return c; - } - break; - } - case PEOPLE_STREQUENT: { - // Build the first query for starred - qb.setTables(PEOPLE_PHONES_JOIN); - qb.setProjectionMap(sPeopleWithMaxTimesContactedProjectionMap); - final String starredQuery = qb.buildQuery(projectionIn, "starred = 1", - null, null, null, null, - null /* limit */); - - qb = new SQLiteQueryBuilder(); - qb.setTables(PEOPLE_PHONES_JOIN); - qb.setProjectionMap(sPeopleProjectionMap); - final String frequentQuery = qb.buildQuery(projectionIn, - "times_contacted > 0 AND starred = 0", null, null, null, null, null); - - final String query = qb.buildUnionQuery(new String[] {starredQuery, frequentQuery}, - STREQUENT_ORDER_BY, STREQUENT_LIMIT); - final SQLiteDatabase db = getDatabase(); - Cursor c = db.rawQueryWithFactory(null, query, null, "people"); - if ((c != null) && !isTemporary()) { - c.setNotificationUri(getContext().getContentResolver(), url); - } - return c; - } - case PEOPLE_STREQUENT_FILTER: { - // Build the first query for starred - qb.setTables(PEOPLE_PHONES_JOIN); - qb.setProjectionMap(sPeopleWithMaxTimesContactedProjectionMap); - if (url.getPathSegments().size() > 3) { - qb.appendWhere(buildPeopleLookupWhereClause(url.getLastPathSegment())); - } - qb.appendWhere(" AND starred = 1"); - final String starredQuery = qb.buildQuery(projectionIn, null, null, null, null, - null, null); - - qb = new SQLiteQueryBuilder(); - qb.setTables(PEOPLE_PHONES_JOIN); - qb.setProjectionMap(sPeopleProjectionMap); - if (url.getPathSegments().size() > 3) { - qb.appendWhere(buildPeopleLookupWhereClause(url.getLastPathSegment())); - } - qb.appendWhere(" AND times_contacted > 0 AND starred = 0"); - final String frequentQuery = qb.buildQuery(projectionIn, null, null, null, null, - null, null); - - final String query = qb.buildUnionQuery(new String[] {starredQuery, frequentQuery}, - STREQUENT_ORDER_BY, null); - final SQLiteDatabase db = getDatabase(); - Cursor c = db.rawQueryWithFactory(null, query, null, sPeopleTable); - if ((c != null) && !isTemporary()) { - c.setNotificationUri(getContext().getContentResolver(), url); - } - return c; - } - case DELETED_PEOPLE: - if (isTemporary()) { - qb.setTables("_deleted_people"); - break; - } - throw new UnsupportedOperationException(); - case PEOPLE_ID: - qb.setTables("people LEFT OUTER JOIN phones ON people.primary_phone=phones._id " - + "LEFT OUTER JOIN presence ON (presence." + Presence.PERSON_ID - + "=people._id)"); - qb.setProjectionMap(sPeopleProjectionMap); - qb.appendWhere("people._id="); - qb.appendWhere(url.getPathSegments().get(1)); - break; - case PEOPLE_PHONES: - qb.setTables("phones, people"); - qb.setProjectionMap(sPhonesProjectionMap); - qb.appendWhere("people._id = phones.person AND person="); - qb.appendWhere(url.getPathSegments().get(1)); - break; - case PEOPLE_PHONES_ID: - qb.setTables("phones, people"); - qb.setProjectionMap(sPhonesProjectionMap); - qb.appendWhere("people._id = phones.person AND person="); - qb.appendWhere(url.getPathSegments().get(1)); - qb.appendWhere(" AND phones._id="); - qb.appendWhere(url.getPathSegments().get(3)); - break; - case PEOPLE_CONTACTMETHODS: - qb.setTables("contact_methods, people"); - qb.setProjectionMap(sContactMethodsProjectionMap); - qb.appendWhere("people._id = contact_methods.person AND person="); - qb.appendWhere(url.getPathSegments().get(1)); - break; - case PEOPLE_CONTACTMETHODS_ID: - qb.setTables("contact_methods, people"); - qb.setProjectionMap(sContactMethodsProjectionMap); - qb.appendWhere("people._id = contact_methods.person AND person="); - qb.appendWhere(url.getPathSegments().get(1)); - qb.appendWhere(" AND contact_methods._id="); - qb.appendWhere(url.getPathSegments().get(3)); - break; - case PEOPLE_ORGANIZATIONS: - qb.setTables("organizations, people"); - qb.setProjectionMap(sOrganizationsProjectionMap); - qb.appendWhere("people._id = organizations.person AND person="); - qb.appendWhere(url.getPathSegments().get(1)); - break; - case PEOPLE_ORGANIZATIONS_ID: - qb.setTables("organizations, people"); - qb.setProjectionMap(sOrganizationsProjectionMap); - qb.appendWhere("people._id = organizations.person AND person="); - qb.appendWhere(url.getPathSegments().get(1)); - qb.appendWhere(" AND organizations._id="); - qb.appendWhere(url.getPathSegments().get(3)); - break; - case PHONES: - qb.setTables("phones, people"); - qb.appendWhere("people._id = phones.person"); - qb.setProjectionMap(sPhonesProjectionMap); - break; - case PHONES_ID: - qb.setTables("phones, people"); - qb.appendWhere("people._id = phones.person AND phones._id=" - + url.getPathSegments().get(1)); - qb.setProjectionMap(sPhonesProjectionMap); - break; - case ORGANIZATIONS: - qb.setTables("organizations, people"); - qb.appendWhere("people._id = organizations.person"); - qb.setProjectionMap(sOrganizationsProjectionMap); - break; - case ORGANIZATIONS_ID: - qb.setTables("organizations, people"); - qb.appendWhere("people._id = organizations.person AND organizations._id=" - + url.getPathSegments().get(1)); - qb.setProjectionMap(sOrganizationsProjectionMap); - break; - case PHONES_MOBILE_FILTER_NAME: - qb.appendWhere("type=" + Contacts.PhonesColumns.TYPE_MOBILE + " AND "); - - // Fall through. - - case PHONES_FILTER_NAME: - qb.setTables("phones JOIN people ON (people._id = phones.person)"); - qb.setProjectionMap(sPhonesProjectionMap); - if (url.getPathSegments().size() > 2) { - qb.appendWhere(buildPeopleLookupWhereClause(url.getLastPathSegment())); - } - break; - - case PHONES_FILTER: { - String phoneNumber = url.getPathSegments().get(2); - String indexable = PhoneNumberUtils.toCallerIDMinMatch(phoneNumber); - StringBuilder subQuery = new StringBuilder(); - if (TextUtils.isEmpty(sort)) { - // Default the sort order to something reasonable so we get consistent - // results when callers don't request an ordering - sort = People.DEFAULT_SORT_ORDER; - } - - subQuery.append("people, (SELECT * FROM phones WHERE (phones.number_key GLOB '"); - subQuery.append(indexable); - subQuery.append("*')) AS phones"); - qb.setTables(subQuery.toString()); - qb.appendWhere("phones.person=people._id AND PHONE_NUMBERS_EQUAL(phones.number, "); - qb.appendWhereEscapeString(phoneNumber); - qb.appendWhere(")"); - qb.setProjectionMap(sPhonesProjectionMap); - break; - } - case CONTACTMETHODS: - qb.setTables("contact_methods, people"); - qb.setProjectionMap(sContactMethodsProjectionMap); - qb.appendWhere("people._id = contact_methods.person"); - break; - case CONTACTMETHODS_ID: - qb.setTables("contact_methods LEFT OUTER JOIN people ON contact_methods.person = people._id"); - qb.setProjectionMap(sContactMethodsProjectionMap); - qb.appendWhere("contact_methods._id="); - qb.appendWhere(url.getPathSegments().get(1)); - break; - case CONTACTMETHODS_EMAIL_FILTER: - String pattern = url.getPathSegments().get(2); - StringBuilder whereClause = new StringBuilder(); - - // TODO This is going to be REALLY slow. Come up with - // something faster. - whereClause.append(ContactMethods.KIND); - whereClause.append('='); - whereClause.append('\''); - whereClause.append(Contacts.KIND_EMAIL); - whereClause.append("' AND (UPPER("); - whereClause.append(ContactMethods.NAME); - whereClause.append(") GLOB "); - DatabaseUtils.appendEscapedSQLString(whereClause, pattern + "*"); - whereClause.append(" OR UPPER("); - whereClause.append(ContactMethods.NAME); - whereClause.append(") GLOB "); - DatabaseUtils.appendEscapedSQLString(whereClause, "* " + pattern + "*"); - whereClause.append(") AND "); - qb.appendWhere(whereClause.toString()); - - // Fall through. - - case CONTACTMETHODS_EMAIL: - qb.setTables("contact_methods INNER JOIN people on (contact_methods.person = people._id)"); - qb.setProjectionMap(sEmailSearchProjectionMap); - qb.appendWhere("kind = " + Contacts.KIND_EMAIL); - qb.setDistinct(true); - break; - - case PEOPLE_CONTACTMETHODS_WITH_PRESENCE: - qb.appendWhere("people._id=?"); - selectionArgs = appendSelectionArg(selectionArgs, url.getPathSegments().get(1)); - // Fall through. - - case CONTACTMETHODS_WITH_PRESENCE: - qb.setTables("contact_methods JOIN people ON (contact_methods.person = people._id)" - + " LEFT OUTER JOIN presence ON " - // Match gtalk presence items - + "((kind=" + Contacts.KIND_EMAIL + - " AND im_protocol='" - + ContactMethods.encodePredefinedImProtocol( - ContactMethods.PROTOCOL_GOOGLE_TALK) - + "' AND data=im_handle)" - + " OR " - // Match IM presence items - + "(kind=" + Contacts.KIND_IM - + " AND data=im_handle AND aux_data=im_protocol))"); - qb.setProjectionMap(sContactMethodsWithPresenceProjectionMap); - break; - - case CALLS: - qb.setTables("calls"); - qb.setProjectionMap(sCallsProjectionMap); - notificationUri = CallLog.CONTENT_URI; - break; - case CALLS_ID: - qb.setTables("calls"); - qb.setProjectionMap(sCallsProjectionMap); - qb.appendWhere("calls._id="); - qb.appendWhere(url.getPathSegments().get(1)); - notificationUri = CallLog.CONTENT_URI; - break; - case CALLS_FILTER: { - qb.setTables("calls"); - qb.setProjectionMap(sCallsProjectionMap); - - String phoneNumber = url.getPathSegments().get(2); - qb.appendWhere("PHONE_NUMBERS_EQUAL(number, "); - qb.appendWhereEscapeString(phoneNumber); - qb.appendWhere(")"); - notificationUri = CallLog.CONTENT_URI; - break; - } - - case PRESENCE: - qb.setTables("presence LEFT OUTER JOIN people on (presence." + Presence.PERSON_ID - + "= people._id)"); - qb.setProjectionMap(sPresenceProjectionMap); - break; - case PRESENCE_ID: - qb.setTables("presence LEFT OUTER JOIN people on (presence." + Presence.PERSON_ID - + "= people._id)"); - qb.appendWhere("presence._id="); - qb.appendWhere(url.getLastPathSegment()); - break; - case VOICE_DIALER_TIMESTAMP: - qb.setTables("voice_dialer_timestamp"); - qb.appendWhere("_id=1"); - break; - - case PEOPLE_EXTENSIONS_ID: - qb.appendWhere("extensions._id=" + url.getPathSegments().get(3) + " AND "); - // fall through - case PEOPLE_EXTENSIONS: - qb.appendWhere("person=" + url.getPathSegments().get(1)); - qb.setTables(sExtensionsTable); - qb.setProjectionMap(sExtensionsProjectionMap); - break; - - case EXTENSIONS_ID: - qb.appendWhere("extensions._id=" + url.getPathSegments().get(1)); - // fall through - case EXTENSIONS: - qb.setTables(sExtensionsTable); - qb.setProjectionMap(sExtensionsProjectionMap); - break; - - case LIVE_FOLDERS_PEOPLE: - qb.setTables("people LEFT OUTER JOIN photos ON (people._id = photos.person)"); - qb.setProjectionMap(sLiveFoldersProjectionMap); - break; - - case LIVE_FOLDERS_PEOPLE_WITH_PHONES: - qb.setTables("people LEFT OUTER JOIN photos ON (people._id = photos.person)"); - qb.setProjectionMap(sLiveFoldersProjectionMap); - qb.appendWhere(People.PRIMARY_PHONE_ID + " IS NOT NULL"); - break; - - case LIVE_FOLDERS_PEOPLE_FAVORITES: - qb.setTables("people LEFT OUTER JOIN photos ON (people._id = photos.person)"); - qb.setProjectionMap(sLiveFoldersProjectionMap); - qb.appendWhere(People.STARRED + " <> 0"); - break; - - case LIVE_FOLDERS_PEOPLE_GROUP_NAME: - qb.setTables("people LEFT OUTER JOIN photos ON (people._id = photos.person)"); - qb.setProjectionMap(sLiveFoldersProjectionMap); - qb.appendWhere("people._id IN (SELECT person FROM groupmembership JOIN groups " + - "ON (group_id=groups._id OR " + - "(group_sync_id = groups._sync_id AND " + - "group_sync_account = groups._sync_account)) "+ - "WHERE " + Groups.NAME + "=" - + DatabaseUtils.sqlEscapeString(url.getLastPathSegment()) + ")"); - break; - - default: - throw new IllegalArgumentException("Unknown URL " + url); - } - - // run the query - final SQLiteDatabase db = getDatabase(); - Cursor c = qb.query(db, projectionIn, selection, selectionArgs, - null, null, sort); - if ((c != null) && !isTemporary()) { - c.setNotificationUri(getContext().getContentResolver(), notificationUri); - } - return c; - } - - private Cursor queryOwner(String[] projection) { - // Check the permissions - getContext().enforceCallingPermission("android.permission.READ_OWNER_DATA", - "No permission to access owner info"); - - // Read the owner id - SharedPreferences prefs = getContext().getSharedPreferences(PREFS_NAME_OWNER, - Context.MODE_PRIVATE); - long ownerId = prefs.getLong(PREF_OWNER_ID, 0); - - // Run the query - return queryInternal(ContentUris.withAppendedId(People.CONTENT_URI, ownerId), projection, - null, null, null); - } - - /** - * Append a string to a selection args array - * - * @param selectionArgs the old arg - * @param newArg the new arg to append - * @return a new string array with all of the args - */ - private String[] appendSelectionArg(String[] selectionArgs, String newArg) { - if (selectionArgs == null || selectionArgs.length == 0) { - return new String[] { newArg }; - } else { - int length = selectionArgs.length; - String[] newArgs = new String[length + 1]; - System.arraycopy(selectionArgs, 0, newArgs, 0, length); - newArgs[length] = newArg; - return newArgs; - } - } - - /** - * Either sets up the query builder so we can run the proper query against the database - * and returns null, or returns a cursor with the results already in it. - * - * @param url the URL passed for the suggestion - * @param qb the query builder to use if a query needs to be run on the database - * @return null with qb configured for a query, a cursor with the results already in it. - */ - private Cursor handleSearchSuggestionsQuery(Uri url, SQLiteQueryBuilder qb) { - qb.setTables("people"); - qb.setProjectionMap(sSearchSuggestionsProjectionMap); - if (url.getPathSegments().size() > 1) { - // A search term was entered, use it to filter - final String searchClause = url.getLastPathSegment(); - if (!TextUtils.isDigitsOnly(searchClause)) { - qb.appendWhere(buildPeopleLookupWhereClause(searchClause)); - } else { - final String[] columnNames = new String[] { - SearchManager.SUGGEST_COLUMN_TEXT_1, - SearchManager.SUGGEST_COLUMN_TEXT_2, - SearchManager.SUGGEST_COLUMN_INTENT_DATA, - SearchManager.SUGGEST_COLUMN_INTENT_ACTION, - }; - -/* - * TODO: figure out how to localize things so myFaves can read the constants when sub classing - */ - ArrayList dialNumber = new ArrayList(); - dialNumber.add("Dial number"); - dialNumber.add("Using " + searchClause); - dialNumber.add("tel:" + searchClause); - dialNumber.add(Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED); - - ArrayList createContact = new ArrayList(); - createContact.add("Create contact"); - createContact.add("Using " + searchClause); - createContact.add("tel:" + searchClause); - createContact.add(Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED); - - ArrayList<ArrayList> rows = new ArrayList<ArrayList>(); - rows.add(dialNumber); - rows.add(createContact); - - ArrayListCursor cursor = new ArrayListCursor(columnNames, rows); - return cursor; - } - } - return null; - } - - @Override - public String getType(Uri url) { - int match = sURIMatcher.match(url); - switch (match) { - case EXTENSIONS: - case PEOPLE_EXTENSIONS: - return Extensions.CONTENT_TYPE; - case EXTENSIONS_ID: - case PEOPLE_EXTENSIONS_ID: - return Extensions.CONTENT_ITEM_TYPE; - case PEOPLE: - return "vnd.android.cursor.dir/person"; - case PEOPLE_ID: - return "vnd.android.cursor.item/person"; - case PEOPLE_PHONES: - return "vnd.android.cursor.dir/phone"; - case PEOPLE_PHONES_ID: - return "vnd.android.cursor.item/phone"; - case PEOPLE_CONTACTMETHODS: - return "vnd.android.cursor.dir/contact-methods"; - case PEOPLE_CONTACTMETHODS_ID: - return getContactMethodType(url); - case PHONES: - return "vnd.android.cursor.dir/phone"; - case PHONES_ID: - return "vnd.android.cursor.item/phone"; - case PHONES_FILTER: - case PHONES_FILTER_NAME: - case PHONES_MOBILE_FILTER_NAME: - return "vnd.android.cursor.dir/phone"; - case CONTACTMETHODS: - return "vnd.android.cursor.dir/contact-methods"; - case CONTACTMETHODS_ID: - return getContactMethodType(url); - case CONTACTMETHODS_EMAIL: - case CONTACTMETHODS_EMAIL_FILTER: - return "vnd.android.cursor.dir/email"; - case CALLS: - return "vnd.android.cursor.dir/calls"; - case CALLS_ID: - return "vnd.android.cursor.item/calls"; - case ORGANIZATIONS: - return "vnd.android.cursor.dir/organizations"; - case ORGANIZATIONS_ID: - return "vnd.android.cursor.item/organization"; - case CALLS_FILTER: - return "vnd.android.cursor.dir/calls"; - default: - throw new IllegalArgumentException("Unknown URL"); - } - } - - private String getContactMethodType(Uri url) - { - String mime = null; - - Cursor c = query(url, new String[] {ContactMethods.KIND}, null, null, null); - if (c != null) { - try { - if (c.moveToFirst()) { - int kind = c.getInt(0); - switch (kind) { - case Contacts.KIND_EMAIL: - mime = "vnd.android.cursor.item/email"; - break; - - case Contacts.KIND_IM: - mime = "vnd.android.cursor.item/jabber-im"; - break; - - case Contacts.KIND_POSTAL: - mime = "vnd.android.cursor.item/postal-address"; - break; - } - } - } finally { - c.close(); - } - } - return mime; - } - - private ContentValues queryAndroidStarredGroupId(String account) { - String whereString; - String[] whereArgs; - if (!TextUtils.isEmpty(account)) { - whereString = "_sync_account=? AND name=?"; - whereArgs = new String[]{account, Groups.GROUP_ANDROID_STARRED}; - } else { - whereString = "_sync_account is null AND name=?"; - whereArgs = new String[]{Groups.GROUP_ANDROID_STARRED}; - } - Cursor cursor = getDatabase().query(sGroupsTable, - new String[]{Groups._ID, Groups._SYNC_ID, Groups._SYNC_ACCOUNT}, - whereString, whereArgs, null, null, null); - try { - if (cursor.moveToNext()) { - ContentValues result = new ContentValues(); - result.put(Groups._ID, cursor.getLong(0)); - result.put(Groups._SYNC_ID, cursor.getString(1)); - result.put(Groups._SYNC_ACCOUNT, cursor.getString(2)); - return result; - } - return null; - } finally { - cursor.close(); - } - } - - @Override - public Uri insertInternal(Uri url, ContentValues initialValues) { - Uri resultUri = null; - long rowID; - - final SQLiteDatabase db = getDatabase(); - int match = sURIMatcher.match(url); - switch (match) { - case PEOPLE_GROUPMEMBERSHIP: - case GROUPMEMBERSHIP: { - mValues.clear(); - mValues.putAll(initialValues); - if (match == PEOPLE_GROUPMEMBERSHIP) { - mValues.put(GroupMembership.PERSON_ID, - Long.valueOf(url.getPathSegments().get(1))); - } - resultUri = insertIntoGroupmembership(mValues); - } - break; - - case PEOPLE_OWNER: - return insertOwner(initialValues); - - case PEOPLE_EXTENSIONS: - case EXTENSIONS: { - ContentValues newMap = new ContentValues(initialValues); - if (match == PEOPLE_EXTENSIONS) { - newMap.put(Extensions.PERSON_ID, - Long.valueOf(url.getPathSegments().get(1))); - } - rowID = mExtensionsInserter.insert(newMap); - if (rowID > 0) { - resultUri = ContentUris.withAppendedId(Extensions.CONTENT_URI, rowID); - } - } - break; - - case PHOTOS: { - if (!isTemporary()) { - throw new UnsupportedOperationException(); - } - rowID = mPhotosInserter.insert(initialValues); - if (rowID > 0) { - resultUri = ContentUris.withAppendedId(Photos.CONTENT_URI, rowID); - } - } - break; - - case GROUPS: { - ContentValues newMap = new ContentValues(initialValues); - ensureSyncAccountIsSet(newMap); - newMap.put(Groups._SYNC_DIRTY, 1); - // Insert into the groups table - rowID = mGroupsInserter.insert(newMap); - if (rowID > 0) { - resultUri = ContentUris.withAppendedId(Groups.CONTENT_URI, rowID); - if (!isTemporary() && newMap.containsKey(Groups.SHOULD_SYNC)) { - final String account = newMap.getAsString(Groups._SYNC_ACCOUNT); - if (!TextUtils.isEmpty(account)) { - final ContentResolver cr = getContext().getContentResolver(); - onLocalChangesForAccount(cr, account, false); - } - } - } - } - break; - - case PEOPLE_RAW: - case PEOPLE: { - mValues.clear(); - mValues.putAll(initialValues); - ensureSyncAccountIsSet(mValues); - mValues.put(People._SYNC_DIRTY, 1); - // Insert into the people table - rowID = mPeopleInserter.insert(mValues); - if (rowID > 0) { - resultUri = ContentUris.withAppendedId(People.CONTENT_URI, rowID); - if (!isTemporary()) { - String account = mValues.getAsString(People._SYNC_ACCOUNT); - Long starredValue = mValues.getAsLong(People.STARRED); - final String syncId = mValues.getAsString(People._SYNC_ID); - boolean isStarred = starredValue != null && starredValue != 0; - fixupGroupMembershipAfterPeopleUpdate(account, rowID, isStarred); - // create a photo row for this person - mDb.delete(sPhotosTable, "person=" + rowID, null); - mValues.clear(); - mValues.put(Photos.PERSON_ID, rowID); - mValues.put(Photos._SYNC_ACCOUNT, account); - mValues.put(Photos._SYNC_ID, syncId); - mValues.put(Photos._SYNC_DIRTY, 0); - mPhotosInserter.insert(mValues); - } - } - } - break; - - case DELETED_PEOPLE: { - if (isTemporary()) { - // Insert into the people table - rowID = db.insert("_deleted_people", "_sync_id", initialValues); - if (rowID > 0) { - resultUri = Uri.parse("content://contacts/_deleted_people/" + rowID); - } - } else { - throw new UnsupportedOperationException(); - } - } - break; - - case DELETED_GROUPS: { - if (isTemporary()) { - rowID = db.insert(sDeletedGroupsTable, Groups._SYNC_ID, - initialValues); - if (rowID > 0) { - resultUri =ContentUris.withAppendedId( - Groups.DELETED_CONTENT_URI, rowID); - } - } else { - throw new UnsupportedOperationException(); - } - } - break; - - case PEOPLE_PHONES: - case PHONES: { - mValues.clear(); - mValues.putAll(initialValues); - if (match == PEOPLE_PHONES) { - mValues.put(Contacts.Phones.PERSON_ID, - Long.valueOf(url.getPathSegments().get(1))); - } - String number = mValues.getAsString(Contacts.Phones.NUMBER); - if (number != null) { - mValues.put("number_key", PhoneNumberUtils.getStrippedReversed(number)); - } - - rowID = insertAndFixupPrimary(Contacts.KIND_PHONE, mValues); - resultUri = ContentUris.withAppendedId(Phones.CONTENT_URI, rowID); - } - break; - - case CONTACTMETHODS: - case PEOPLE_CONTACTMETHODS: { - mValues.clear(); - mValues.putAll(initialValues); - if (match == PEOPLE_CONTACTMETHODS) { - mValues.put("person", url.getPathSegments().get(1)); - } - Integer kind = mValues.getAsInteger(ContactMethods.KIND); - if (kind == null) { - throw new IllegalArgumentException("you must specify the ContactMethods.KIND"); - } - rowID = insertAndFixupPrimary(kind, mValues); - if (rowID > 0) { - resultUri = ContentUris.withAppendedId(ContactMethods.CONTENT_URI, rowID); - } - } - break; - - case CALLS: { - rowID = mCallsInserter.insert(initialValues); - if (rowID > 0) { - resultUri = Uri.parse("content://call_log/calls/" + rowID); - } - } - break; - - case PRESENCE: { - final String handle = initialValues.getAsString(Presence.IM_HANDLE); - final String protocol = initialValues.getAsString(Presence.IM_PROTOCOL); - if (TextUtils.isEmpty(handle) || TextUtils.isEmpty(protocol)) { - throw new IllegalArgumentException("IM_PROTOCOL and IM_HANDLE are required"); - } - - // Look for the contact for this presence update - StringBuilder query = new StringBuilder("SELECT "); - query.append(ContactMethods.PERSON_ID); - query.append(" FROM contact_methods WHERE (kind="); - query.append(Contacts.KIND_IM); - query.append(" AND "); - query.append(ContactMethods.DATA); - query.append("=? AND "); - query.append(ContactMethods.AUX_DATA); - query.append("=?)"); - - String[] selectionArgs; - if (GTALK_PROTOCOL_STRING.equals(protocol)) { - // For gtalk accounts we usually don't have an explicit IM - // entry, so also look for the email address as well - query.append(" OR ("); - query.append("kind="); - query.append(Contacts.KIND_EMAIL); - query.append(" AND "); - query.append(ContactMethods.DATA); - query.append("=?)"); - selectionArgs = new String[] { handle, protocol, handle }; - } else { - selectionArgs = new String[] { handle, protocol }; - } - - Cursor c = db.rawQueryWithFactory(null, query.toString(), selectionArgs, null); - - long personId = 0; - try { - if (c.moveToFirst()) { - personId = c.getLong(0); - } else { - // No contact found, return a null URI - return null; - } - } finally { - c.close(); - } - - mValues.clear(); - mValues.putAll(initialValues); - mValues.put(Presence.PERSON_ID, personId); - - // Insert the presence update - rowID = db.replace("presence", null, mValues); - if (rowID > 0) { - resultUri = Uri.parse("content://contacts/presence/" + rowID); - } - } - break; - - case PEOPLE_ORGANIZATIONS: - case ORGANIZATIONS: { - ContentValues newMap = new ContentValues(initialValues); - if (match == PEOPLE_ORGANIZATIONS) { - newMap.put(Contacts.Phones.PERSON_ID, - Long.valueOf(url.getPathSegments().get(1))); - } - rowID = insertAndFixupPrimary(Contacts.KIND_ORGANIZATION, newMap); - if (rowID > 0) { - resultUri = Uri.parse("content://contacts/organizations/" + rowID); - } - } - break; - default: - throw new UnsupportedOperationException("Cannot insert into URL: " + url); - } - - return resultUri; - } - - @Override - protected void onAccountsChanged(String[] accountsArray) { - super.onAccountsChanged(accountsArray); - synchronized (mAccountsLock) { - mAccounts = new String[accountsArray.length]; - System.arraycopy(accountsArray, 0, mAccounts, 0, mAccounts.length); - } - } - - private void ensureSyncAccountIsSet(ContentValues values) { - synchronized (mAccountsLock) { - String account = values.getAsString(SyncConstValue._SYNC_ACCOUNT); - if (account == null && mAccounts.length > 0) { - values.put(SyncConstValue._SYNC_ACCOUNT, mAccounts[0]); - } - } - } - - private Uri insertOwner(ContentValues values) { - // Check the permissions - getContext().enforceCallingPermission("android.permission.WRITE_OWNER_DATA", - "No permission to set owner info"); - - // Insert the owner info - Uri uri = insertInternal(People.CONTENT_URI, values); - - // Record which person is the owner - long id = ContentUris.parseId(uri); - SharedPreferences.Editor prefs = getContext().getSharedPreferences(PREFS_NAME_OWNER, - Context.MODE_PRIVATE).edit(); - prefs.putLong(PREF_OWNER_ID, id); - prefs.commit(); - return uri; - } - - private Uri insertIntoGroupmembership(ContentValues values) { - String groupSyncAccount = values.getAsString(GroupMembership.GROUP_SYNC_ACCOUNT); - String groupSyncId = values.getAsString(GroupMembership.GROUP_SYNC_ID); - final Long personId = values.getAsLong(GroupMembership.PERSON_ID); - if (!values.containsKey(GroupMembership.GROUP_ID)) { - if (TextUtils.isEmpty(groupSyncAccount) || TextUtils.isEmpty(groupSyncId)) { - throw new IllegalArgumentException( - "insertIntoGroupmembership: no GROUP_ID wasn't specified and non-empty " - + "GROUP_SYNC_ID and GROUP_SYNC_ACCOUNT fields weren't specifid, " - + values); - } - if (0 != DatabaseUtils.longForQuery(getDatabase(), "" - + "SELECT COUNT(*) " - + "FROM groupmembership " - + "WHERE group_sync_id=? AND person=?", - new String[]{groupSyncId, String.valueOf(personId)})) { - final String errorMessage = - "insertIntoGroupmembership: a row with this server key already exists, " - + values; - if (Config.LOGD) Log.d(TAG, errorMessage); - return null; - } - } else { - long groupId = values.getAsLong(GroupMembership.GROUP_ID); - if (!TextUtils.isEmpty(groupSyncAccount) || !TextUtils.isEmpty(groupSyncId)) { - throw new IllegalArgumentException( - "insertIntoGroupmembership: GROUP_ID was specified but " - + "GROUP_SYNC_ID and GROUP_SYNC_ACCOUNT fields were also specifid, " - + values); - } - if (0 != DatabaseUtils.longForQuery(getDatabase(), - "SELECT COUNT(*) FROM groupmembership where group_id=? AND person=?", - new String[]{String.valueOf(groupId), String.valueOf(personId)})) { - final String errorMessage = - "insertIntoGroupmembership: a row with this local key already exists, " - + values; - if (Config.LOGD) Log.d(TAG, errorMessage); - return null; - } - } - - long rowId = mGroupMembershipInserter.insert(values); - if (rowId <= 0) { - final String errorMessage = "insertIntoGroupmembership: the insert failed, values are " - + values; - if (Config.LOGD) Log.d(TAG, errorMessage); - return null; - } - - // set the STARRED column in the people row if this group is the GROUP_ANDROID_STARRED - if (!isTemporary() && queryGroupMembershipContainsStarred(personId)) { - fixupPeopleStarred(personId, true); - } - - return ContentUris.withAppendedId(GroupMembership.CONTENT_URI, rowId); - } - - private void fixupGroupMembershipAfterPeopleUpdate(String account, long personId, - boolean makeStarred) { - ContentValues starredGroupInfo = queryAndroidStarredGroupId(account); - if (makeStarred) { - if (starredGroupInfo == null) { - // we need to add the starred group - mValuesLocal.clear(); - mValuesLocal.put(Groups.NAME, Groups.GROUP_ANDROID_STARRED); - mValuesLocal.put(Groups._SYNC_DIRTY, 1); - mValuesLocal.put(Groups._SYNC_ACCOUNT, account); - long groupId = mGroupsInserter.insert(mValuesLocal); - starredGroupInfo = new ContentValues(); - starredGroupInfo.put(Groups._ID, groupId); - starredGroupInfo.put(Groups._SYNC_ACCOUNT, account); - // don't put the _SYNC_ID in here since we don't know it yet - } - - final Long groupId = starredGroupInfo.getAsLong(Groups._ID); - final String syncId = starredGroupInfo.getAsString(Groups._SYNC_ID); - final String syncAccount = starredGroupInfo.getAsString(Groups._SYNC_ACCOUNT); - - // check that either groupId is set or the syncId/Account is set - final boolean hasSyncId = !TextUtils.isEmpty(syncId); - final boolean hasGroupId = groupId != null; - if (!hasGroupId && !hasSyncId) { - throw new IllegalStateException("at least one of the groupId or " - + "the syncId must be set, " + starredGroupInfo); - } - - // now add this person to the group - mValuesLocal.clear(); - mValuesLocal.put(GroupMembership.PERSON_ID, personId); - mValuesLocal.put(GroupMembership.GROUP_ID, groupId); - mValuesLocal.put(GroupMembership.GROUP_SYNC_ID, syncId); - mValuesLocal.put(GroupMembership.GROUP_SYNC_ACCOUNT, syncAccount); - mGroupMembershipInserter.insert(mValuesLocal); - } else { - if (starredGroupInfo != null) { - // delete the groupmembership rows for this person that match the starred group id - String syncAccount = starredGroupInfo.getAsString(Groups._SYNC_ACCOUNT); - String syncId = starredGroupInfo.getAsString(Groups._SYNC_ID); - if (!TextUtils.isEmpty(syncId)) { - mDb.delete(sGroupmembershipTable, - "person=? AND group_sync_id=? AND group_sync_account=?", - new String[]{String.valueOf(personId), syncId, syncAccount}); - } else { - mDb.delete(sGroupmembershipTable, "person=? AND group_id=?", - new String[]{ - Long.toString(personId), - Long.toString(starredGroupInfo.getAsLong(Groups._ID))}); - } - } - } - } - - private int fixupPeopleStarred(long personId, boolean inStarredGroup) { - mValuesLocal.clear(); - mValuesLocal.put(People.STARRED, inStarredGroup ? 1 : 0); - return getDatabase().update(sPeopleTable, mValuesLocal, WHERE_ID, - new String[]{String.valueOf(personId)}); - } - - private String kindToTable(int kind) { - switch (kind) { - case Contacts.KIND_EMAIL: return sContactMethodsTable; - case Contacts.KIND_POSTAL: return sContactMethodsTable; - case Contacts.KIND_IM: return sContactMethodsTable; - case Contacts.KIND_PHONE: return sPhonesTable; - case Contacts.KIND_ORGANIZATION: return sOrganizationsTable; - default: throw new IllegalArgumentException("unknown kind, " + kind); - } - } - - private DatabaseUtils.InsertHelper kindToInserter(int kind) { - switch (kind) { - case Contacts.KIND_EMAIL: return mContactMethodsInserter; - case Contacts.KIND_POSTAL: return mContactMethodsInserter; - case Contacts.KIND_IM: return mContactMethodsInserter; - case Contacts.KIND_PHONE: return mPhonesInserter; - case Contacts.KIND_ORGANIZATION: return mOrganizationsInserter; - default: throw new IllegalArgumentException("unknown kind, " + kind); - } - } - - private long insertAndFixupPrimary(int kind, ContentValues values) { - final String table = kindToTable(kind); - boolean isPrimary = false; - Long personId = null; - - if (!isTemporary()) { - // when you add a item, if isPrimary or if there is no primary, - // make this it, set the isPrimary flag, and clear other primary flags - isPrimary = values.containsKey("isprimary") - && (values.getAsInteger("isprimary") != 0); - personId = values.getAsLong("person"); - if (!isPrimary) { - // make it primary anyway if this person doesn't have any rows of this type yet - StringBuilder sb = new StringBuilder("person=" + personId); - if (sContactMethodsTable.equals(table)) { - sb.append(" AND kind="); - sb.append(kind); - } - final boolean isFirstRowOfType = DatabaseUtils.longForQuery(getDatabase(), - "SELECT count(*) FROM " + table + " where " + sb.toString(), null) == 0; - isPrimary = isFirstRowOfType; - } - - values.put("isprimary", isPrimary ? 1 : 0); - } - - // do the actual insert - long newRowId = kindToInserter(kind).insert(values); - - if (newRowId <= 0) { - throw new RuntimeException("error while inserting into " + table + ", " + values); - } - - if (!isTemporary()) { - // If this row was made the primary then clear the other isprimary flags and update - // corresponding people row, if necessary. - if (isPrimary) { - clearOtherIsPrimary(kind, personId, newRowId); - if (kind == Contacts.KIND_PHONE) { - updatePeoplePrimary(personId, People.PRIMARY_PHONE_ID, newRowId); - } else if (kind == Contacts.KIND_EMAIL) { - updatePeoplePrimary(personId, People.PRIMARY_EMAIL_ID, newRowId); - } else if (kind == Contacts.KIND_ORGANIZATION) { - updatePeoplePrimary(personId, People.PRIMARY_ORGANIZATION_ID, newRowId); - } - } - } - - return newRowId; - } - - @Override - public int deleteInternal(Uri url, String userWhere, String[] whereArgs) { - String tableToChange; - String changedItemId; - - final int matchedUriId = sURIMatcher.match(url); - switch (matchedUriId) { - case GROUPMEMBERSHIP_ID: - return deleteFromGroupMembership(Long.parseLong(url.getPathSegments().get(1)), - userWhere, whereArgs); - case GROUPS: - return deleteFromGroups(userWhere, whereArgs); - case GROUPS_ID: - changedItemId = url.getPathSegments().get(1); - return deleteFromGroups(addIdToWhereClause(changedItemId, userWhere), whereArgs); - case EXTENSIONS: - tableToChange = sExtensionsTable; - changedItemId = null; - break; - case EXTENSIONS_ID: - tableToChange = sExtensionsTable; - changedItemId = url.getPathSegments().get(1); - break; - case PEOPLE_RAW: - case PEOPLE: - return deleteFromPeople(null, userWhere, whereArgs); - case PEOPLE_ID: - return deleteFromPeople(url.getPathSegments().get(1), userWhere, whereArgs); - case PEOPLE_PHONES_ID: - tableToChange = sPhonesTable; - changedItemId = url.getPathSegments().get(3); - break; - case PEOPLE_CONTACTMETHODS_ID: - tableToChange = sContactMethodsTable; - changedItemId = url.getPathSegments().get(3); - break; - case PHONES_ID: - tableToChange = sPhonesTable; - changedItemId = url.getPathSegments().get(1); - break; - case ORGANIZATIONS_ID: - tableToChange = sOrganizationsTable; - changedItemId = url.getPathSegments().get(1); - break; - case CONTACTMETHODS_ID: - tableToChange = sContactMethodsTable; - changedItemId = url.getPathSegments().get(1); - break; - case PRESENCE: - tableToChange = "presence"; - changedItemId = null; - break; - case CALLS: - tableToChange = "calls"; - changedItemId = null; - break; - default: - throw new UnsupportedOperationException("Cannot delete that URL: " + url); - } - - String where = addIdToWhereClause(changedItemId, userWhere); - IsPrimaryInfo oldPrimaryInfo = null; - switch (matchedUriId) { - case PEOPLE_PHONES_ID: - case PHONES_ID: - case ORGANIZATIONS_ID: - oldPrimaryInfo = lookupIsPrimaryInfo(tableToChange, - sIsPrimaryProjectionWithoutKind, where, whereArgs); - break; - - case PEOPLE_CONTACTMETHODS_ID: - case CONTACTMETHODS_ID: - oldPrimaryInfo = lookupIsPrimaryInfo(tableToChange, - sIsPrimaryProjectionWithKind, where, whereArgs); - break; - } - - final SQLiteDatabase db = getDatabase(); - int count = db.delete(tableToChange, where, whereArgs); - if (count > 0) { - if (oldPrimaryInfo != null && oldPrimaryInfo.isPrimary) { - fixupPrimaryAfterDelete(oldPrimaryInfo.kind, - oldPrimaryInfo.id, oldPrimaryInfo.person); - } - } - - return count; - } - - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - int match = sURIMatcher.match(uri); - switch (match) { - default: - throw new UnsupportedOperationException(uri.toString()); - } - } - - private int deleteFromGroupMembership(long rowId, String where, String[] whereArgs) { - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables("groups, groupmembership"); - qb.setProjectionMap(sGroupMembershipProjectionMap); - qb.appendWhere(sGroupsJoinString); - qb.appendWhere(" AND groupmembership._id=" + rowId); - Cursor cursor = qb.query(getDatabase(), null, where, whereArgs, null, null, null); - try { - final int indexPersonId = cursor.getColumnIndexOrThrow(GroupMembership.PERSON_ID); - final int indexName = cursor.getColumnIndexOrThrow(GroupMembership.NAME); - while (cursor.moveToNext()) { - if (Groups.GROUP_ANDROID_STARRED.equals(cursor.getString(indexName))) { - fixupPeopleStarred(cursor.getLong(indexPersonId), false); - } - } - } finally { - cursor.close(); - } - - return mDb.delete(sGroupmembershipTable, - addIdToWhereClause(String.valueOf(rowId), where), - whereArgs); - } - - private int deleteFromPeople(String rowId, String where, String[] whereArgs) { - final SQLiteDatabase db = getDatabase(); - where = addIdToWhereClause(rowId, where); - Cursor cursor = db.query(sPeopleTable, null, where, whereArgs, null, null, null); - try { - final int idxSyncId = cursor.getColumnIndexOrThrow(People._SYNC_ID); - final int idxSyncAccount = cursor.getColumnIndexOrThrow(People._SYNC_ACCOUNT); - final int idxSyncVersion = cursor.getColumnIndexOrThrow(People._SYNC_VERSION); - final int dstIdxSyncId = mDeletedPeopleInserter.getColumnIndex(SyncConstValue._SYNC_ID); - final int dstIdxSyncAccount = - mDeletedPeopleInserter.getColumnIndex(SyncConstValue._SYNC_ACCOUNT); - final int dstIdxSyncVersion = - mDeletedPeopleInserter.getColumnIndex(SyncConstValue._SYNC_VERSION); - while (cursor.moveToNext()) { - final String syncId = cursor.getString(idxSyncId); - if (TextUtils.isEmpty(syncId)) continue; - // insert into deleted table - mDeletedPeopleInserter.prepareForInsert(); - mDeletedPeopleInserter.bind(dstIdxSyncId, syncId); - mDeletedPeopleInserter.bind(dstIdxSyncAccount, cursor.getString(idxSyncAccount)); - mDeletedPeopleInserter.bind(dstIdxSyncVersion, cursor.getString(idxSyncVersion)); - mDeletedPeopleInserter.execute(); - } - } finally { - cursor.close(); - } - - // perform the actual delete - return db.delete(sPeopleTable, where, whereArgs); - } - - private int deleteFromGroups(String where, String[] whereArgs) { - HashSet<String> modifiedAccounts = Sets.newHashSet(); - Cursor cursor = getDatabase().query(sGroupsTable, null, where, whereArgs, - null, null, null); - try { - final int indexName = cursor.getColumnIndexOrThrow(Groups.NAME); - final int indexSyncAccount = cursor.getColumnIndexOrThrow(Groups._SYNC_ACCOUNT); - final int indexSyncId = cursor.getColumnIndexOrThrow(Groups._SYNC_ID); - final int indexId = cursor.getColumnIndexOrThrow(Groups._ID); - final int indexShouldSync = cursor.getColumnIndexOrThrow(Groups.SHOULD_SYNC); - while (cursor.moveToNext()) { - String oldName = cursor.getString(indexName); - String syncAccount = cursor.getString(indexSyncAccount); - String syncId = cursor.getString(indexSyncId); - boolean shouldSync = cursor.getLong(indexShouldSync) != 0; - long id = cursor.getLong(indexId); - fixupPeopleStarredOnGroupRename(oldName, null, id); - if (!TextUtils.isEmpty(syncAccount) && !TextUtils.isEmpty(syncId)) { - fixupPeopleStarredOnGroupRename(oldName, null, syncAccount, syncId); - } - if (!TextUtils.isEmpty(syncAccount) && shouldSync) { - modifiedAccounts.add(syncAccount); - } - } - } finally { - cursor.close(); - } - - int numRows = mDb.delete(sGroupsTable, where, whereArgs); - if (numRows > 0) { - if (!isTemporary()) { - final ContentResolver cr = getContext().getContentResolver(); - for (String account : modifiedAccounts) { - onLocalChangesForAccount(cr, account, true); - } - } - } - return numRows; - } - - /** - * Called when local changes are made, so subclasses have - * an opportunity to react as they see fit. - * - * @param resolver the content resolver to use - * @param account the account the changes are tied to - */ - protected void onLocalChangesForAccount(final ContentResolver resolver, String account, - boolean groupsModified) { - // Do nothing - } - - private void fixupPrimaryAfterDelete(int kind, Long itemId, Long personId) { - final String table = kindToTable(kind); - // when you delete an item with isPrimary, - // select a new one as isPrimary and clear the primary if no more items - Long newPrimaryId = findNewPrimary(kind, personId, itemId); - - // we found a new primary, set its isprimary flag - if (newPrimaryId != null) { - mValuesLocal.clear(); - mValuesLocal.put("isprimary", 1); - if (getDatabase().update(table, mValuesLocal, "_id=" + newPrimaryId, null) != 1) { - throw new RuntimeException("error updating " + table + ", _id " - + newPrimaryId + ", values " + mValuesLocal); - } - } - - // if this kind's primary status should be reflected in the people row, update it - if (kind == Contacts.KIND_PHONE) { - updatePeoplePrimary(personId, People.PRIMARY_PHONE_ID, newPrimaryId); - } else if (kind == Contacts.KIND_EMAIL) { - updatePeoplePrimary(personId, People.PRIMARY_EMAIL_ID, newPrimaryId); - } else if (kind == Contacts.KIND_ORGANIZATION) { - updatePeoplePrimary(personId, People.PRIMARY_ORGANIZATION_ID, newPrimaryId); - } - } - - @Override - public int updateInternal(Uri url, ContentValues values, String userWhere, String[] whereArgs) { - final SQLiteDatabase db = getDatabase(); - String tableToChange; - String changedItemId; - final int matchedUriId = sURIMatcher.match(url); - switch (matchedUriId) { - case GROUPS_ID: - changedItemId = url.getPathSegments().get(1); - return updateGroups(values, - addIdToWhereClause(changedItemId, userWhere), whereArgs); - - case PEOPLE_EXTENSIONS_ID: - tableToChange = sExtensionsTable; - changedItemId = url.getPathSegments().get(3); - break; - - case EXTENSIONS_ID: - tableToChange = sExtensionsTable; - changedItemId = url.getPathSegments().get(1); - break; - - case PEOPLE_UPDATE_CONTACT_TIME: - if (values.size() != 1 || !values.containsKey(People.LAST_TIME_CONTACTED)) { - throw new IllegalArgumentException( - "You may only use " + url + " to update People.LAST_TIME_CONTACTED"); - } - tableToChange = sPeopleTable; - changedItemId = url.getPathSegments().get(1); - break; - - case PEOPLE_ID: - mValues.clear(); - mValues.putAll(values); - mValues.put(Photos._SYNC_DIRTY, 1); - values = mValues; - tableToChange = sPeopleTable; - changedItemId = url.getPathSegments().get(1); - break; - - case PEOPLE_PHONES_ID: - tableToChange = sPhonesTable; - changedItemId = url.getPathSegments().get(3); - break; - - case PEOPLE_CONTACTMETHODS_ID: - tableToChange = sContactMethodsTable; - changedItemId = url.getPathSegments().get(3); - break; - - case PHONES_ID: - tableToChange = sPhonesTable; - changedItemId = url.getPathSegments().get(1); - break; - - case PEOPLE_PHOTO: - case PHOTOS_ID: - mValues.clear(); - mValues.putAll(values); - - // The _SYNC_DIRTY flag should only be set if the data was modified and if - // it isn't already provided. - if (!mValues.containsKey(Photos._SYNC_DIRTY) && mValues.containsKey(Photos.DATA)) { - mValues.put(Photos._SYNC_DIRTY, 1); - } - StringBuilder where; - if (matchedUriId == PEOPLE_PHOTO) { - where = new StringBuilder("_id=" + url.getPathSegments().get(1)); - } else { - where = new StringBuilder("person=" + url.getPathSegments().get(1)); - } - if (!TextUtils.isEmpty(userWhere)) { - where.append(" AND ("); - where.append(userWhere); - where.append(')'); - } - return db.update(sPhotosTable, mValues, where.toString(), whereArgs); - - case ORGANIZATIONS_ID: - tableToChange = sOrganizationsTable; - changedItemId = url.getPathSegments().get(1); - break; - - case CONTACTMETHODS_ID: - tableToChange = sContactMethodsTable; - changedItemId = url.getPathSegments().get(1); - break; - - case SETTINGS: - if (whereArgs != null) { - throw new IllegalArgumentException( - "you aren't allowed to specify where args when updating settings"); - } - if (userWhere != null) { - throw new IllegalArgumentException( - "you aren't allowed to specify a where string when updating settings"); - } - return updateSettings(values); - - case CALLS: - tableToChange = "calls"; - changedItemId = null; - break; - - case CALLS_ID: - tableToChange = "calls"; - changedItemId = url.getPathSegments().get(1); - break; - - default: - throw new UnsupportedOperationException("Cannot update URL: " + url); - } - - String where = addIdToWhereClause(changedItemId, userWhere); - int numRowsUpdated = db.update(tableToChange, values, where, whereArgs); - - if (numRowsUpdated > 0 && changedItemId != null) { - long itemId = Long.parseLong(changedItemId); - switch (matchedUriId) { - case ORGANIZATIONS_ID: - fixupPrimaryAfterUpdate( - Contacts.KIND_ORGANIZATION, null, itemId, - values.getAsInteger(Organizations.ISPRIMARY)); - break; - - case PHONES_ID: - case PEOPLE_PHONES_ID: - fixupPrimaryAfterUpdate( - Contacts.KIND_PHONE, matchedUriId == PEOPLE_PHONES_ID - ? Long.parseLong(url.getPathSegments().get(1)) - : null, itemId, - values.getAsInteger(Phones.ISPRIMARY)); - break; - - case CONTACTMETHODS_ID: - case PEOPLE_CONTACTMETHODS_ID: - IsPrimaryInfo isPrimaryInfo = lookupIsPrimaryInfo(sContactMethodsTable, - sIsPrimaryProjectionWithKind, where, whereArgs); - fixupPrimaryAfterUpdate( - isPrimaryInfo.kind, isPrimaryInfo.person, itemId, - values.getAsInteger(ContactMethods.ISPRIMARY)); - break; - - case PEOPLE_ID: - boolean hasStarred = values.containsKey(People.STARRED); - boolean hasPrimaryPhone = values.containsKey(People.PRIMARY_PHONE_ID); - boolean hasPrimaryOrganization = - values.containsKey(People.PRIMARY_ORGANIZATION_ID); - boolean hasPrimaryEmail = values.containsKey(People.PRIMARY_EMAIL_ID); - if (hasStarred || hasPrimaryPhone || hasPrimaryOrganization - || hasPrimaryEmail) { - Cursor c = mDb.query(sPeopleTable, null, - where, whereArgs, null, null, null); - try { - int indexAccount = c.getColumnIndexOrThrow(People._SYNC_ACCOUNT); - int indexId = c.getColumnIndexOrThrow(People._ID); - Long starredValue = values.getAsLong(People.STARRED); - Long primaryPhone = values.getAsLong(People.PRIMARY_PHONE_ID); - Long primaryOrganization = - values.getAsLong(People.PRIMARY_ORGANIZATION_ID); - Long primaryEmail = values.getAsLong(People.PRIMARY_EMAIL_ID); - while (c.moveToNext()) { - final long personId = c.getLong(indexId); - if (hasStarred) { - fixupGroupMembershipAfterPeopleUpdate(c.getString(indexAccount), - personId, starredValue != null && starredValue != 0); - } - - if (hasPrimaryPhone) { - if (primaryPhone == null) { - throw new IllegalArgumentException( - "the value of PRIMARY_PHONE_ID must not be null"); - } - setIsPrimary(Contacts.KIND_PHONE, personId, primaryPhone); - } - if (hasPrimaryOrganization) { - if (primaryOrganization == null) { - throw new IllegalArgumentException( - "the value of PRIMARY_ORGANIZATION_ID must " - + "not be null"); - } - setIsPrimary(Contacts.KIND_ORGANIZATION, personId, - primaryOrganization); - } - if (hasPrimaryEmail) { - if (primaryEmail == null) { - throw new IllegalArgumentException( - "the value of PRIMARY_EMAIL_ID must not be null"); - } - setIsPrimary(Contacts.KIND_EMAIL, personId, primaryEmail); - } - } - } finally { - c.close(); - } - } - break; - } - } - - return numRowsUpdated; - } - - private int updateSettings(ContentValues values) { - final SQLiteDatabase db = getDatabase(); - final String account = values.getAsString(Contacts.Settings._SYNC_ACCOUNT); - final String key = values.getAsString(Contacts.Settings.KEY); - if (key == null) { - throw new IllegalArgumentException("you must specify the key when updating settings"); - } - if (account == null) { - db.delete(sSettingsTable, "_sync_account IS NULL AND key=?", new String[]{key}); - } else { - if (TextUtils.isEmpty(account)) { - throw new IllegalArgumentException("account cannot be the empty string, " + values); - } - db.delete(sSettingsTable, "_sync_account=? AND key=?", new String[]{account, key}); - } - long rowId = db.insert(sSettingsTable, Contacts.Settings.KEY, values); - if (rowId < 0) { - throw new SQLException("error updating settings with " + values); - } - return 1; - } - - private int updateGroups(ContentValues values, String where, String[] whereArgs) { - for (Map.Entry<String, Object> entry : values.valueSet()) { - final String column = entry.getKey(); - if (!Groups.NAME.equals(column) && !Groups.NOTES.equals(column) - && !Groups.SYSTEM_ID.equals(column) && !Groups.SHOULD_SYNC.equals(column)) { - throw new IllegalArgumentException( - "you are not allowed to change column " + column); - } - } - - Set<String> modifiedAccounts = Sets.newHashSet(); - final SQLiteDatabase db = getDatabase(); - if (values.containsKey(Groups.NAME) || values.containsKey(Groups.SHOULD_SYNC)) { - String newName = values.getAsString(Groups.NAME); - Cursor cursor = db.query(sGroupsTable, null, where, whereArgs, null, null, null); - try { - final int indexName = cursor.getColumnIndexOrThrow(Groups.NAME); - final int indexSyncAccount = cursor.getColumnIndexOrThrow(Groups._SYNC_ACCOUNT); - final int indexSyncId = cursor.getColumnIndexOrThrow(Groups._SYNC_ID); - final int indexId = cursor.getColumnIndexOrThrow(Groups._ID); - while (cursor.moveToNext()) { - String syncAccount = cursor.getString(indexSyncAccount); - if (values.containsKey(Groups.NAME)) { - String oldName = cursor.getString(indexName); - String syncId = cursor.getString(indexSyncId); - long id = cursor.getLong(indexId); - fixupPeopleStarredOnGroupRename(oldName, newName, id); - if (!TextUtils.isEmpty(syncAccount) && !TextUtils.isEmpty(syncId)) { - fixupPeopleStarredOnGroupRename(oldName, newName, syncAccount, syncId); - } - } - if (!TextUtils.isEmpty(syncAccount) && values.containsKey(Groups.SHOULD_SYNC)) { - modifiedAccounts.add(syncAccount); - } - } - } finally { - cursor.close(); - } - } - - int numRows = db.update(sGroupsTable, values, where, whereArgs); - if (numRows > 0) { - if (!isTemporary()) { - final ContentResolver cr = getContext().getContentResolver(); - for (String account : modifiedAccounts) { - onLocalChangesForAccount(cr, account, true); - } - } - } - return numRows; - } - - void fixupPeopleStarredOnGroupRename(String oldName, String newName, - String where, String[] whereArgs) { - if (TextUtils.equals(oldName, newName)) return; - - int starredValue; - if (Groups.GROUP_ANDROID_STARRED.equals(newName)) { - starredValue = 1; - } else if (Groups.GROUP_ANDROID_STARRED.equals(oldName)) { - starredValue = 0; - } else { - return; - } - - getDatabase().execSQL("UPDATE people SET starred=" + starredValue + " WHERE _id in (" - + "SELECT person " - + "FROM groups, groupmembership " - + "WHERE " + where + " AND " + sGroupsJoinString + ")", - whereArgs); - } - - void fixupPeopleStarredOnGroupRename(String oldName, String newName, - String syncAccount, String syncId) { - fixupPeopleStarredOnGroupRename(oldName, newName, "_sync_account=? AND _sync_id=?", - new String[]{syncAccount, syncId}); - } - - void fixupPeopleStarredOnGroupRename(String oldName, String newName, long groupId) { - fixupPeopleStarredOnGroupRename(oldName, newName, "group_id=?", - new String[]{String.valueOf(groupId)}); - } - - private void fixupPrimaryAfterUpdate(int kind, Long personId, Long changedItemId, - Integer isPrimaryValue) { - final String table = kindToTable(kind); - - // - when you update isPrimary to true, - // make the changed item the primary, clear others - // - when you update isPrimary to false, - // select a new one as isPrimary, clear the primary if no more phones - if (isPrimaryValue != null) { - if (personId == null) { - personId = lookupPerson(table, changedItemId); - } - - boolean isPrimary = isPrimaryValue != 0; - Long newPrimary = changedItemId; - if (!isPrimary) { - newPrimary = findNewPrimary(kind, personId, changedItemId); - } - clearOtherIsPrimary(kind, personId, changedItemId); - - if (kind == Contacts.KIND_PHONE) { - updatePeoplePrimary(personId, People.PRIMARY_PHONE_ID, newPrimary); - } else if (kind == Contacts.KIND_EMAIL) { - updatePeoplePrimary(personId, People.PRIMARY_EMAIL_ID, newPrimary); - } else if (kind == Contacts.KIND_ORGANIZATION) { - updatePeoplePrimary(personId, People.PRIMARY_ORGANIZATION_ID, newPrimary); - } - } - } - - /** - * Queries table to find the value of the person column for the row with _id. There must - * be exactly one row that matches this id. - * @param table the table to query - * @param id the id of the row to query - * @return the value of the person column for the specified row, returned as a String. - */ - private long lookupPerson(String table, long id) { - return DatabaseUtils.longForQuery( - getDatabase(), - "SELECT person FROM " + table + " where _id=" + id, - null); - } - - /** - * Used to pass around information about a row that has the isprimary column. - */ - private class IsPrimaryInfo { - boolean isPrimary; - Long person; - Long id; - Integer kind; - } - - /** - * Queries the table to determine the state of the row's isprimary column and the kind. - * The where and whereArgs must be sufficient to match either 0 or 1 row. - * @param table the table of rows to consider, supports "phones" and "contact_methods" - * @param projection the projection to use to get the columns that pertain to table - * @param where used in conjunction with the whereArgs to identify the row - * @param where used in conjunction with the where string to identify the row - * @return the IsPrimaryInfo about the matched row, or null if no row was matched - */ - private IsPrimaryInfo lookupIsPrimaryInfo(String table, String[] projection, String where, - String[] whereArgs) { - Cursor cursor = getDatabase().query(table, projection, where, whereArgs, null, null, null); - try { - if (!(cursor.getCount() <= 1)) { - throw new IllegalArgumentException("expected only zero or one rows, got " - + DatabaseUtils.dumpCursorToString(cursor)); - } - if (!cursor.moveToFirst()) return null; - IsPrimaryInfo info = new IsPrimaryInfo(); - info.isPrimary = cursor.getInt(0) != 0; - info.person = cursor.getLong(1); - info.id = cursor.getLong(2); - if (projection == sIsPrimaryProjectionWithKind) { - info.kind = cursor.getInt(3); - } else { - if (sPhonesTable.equals(table)) { - info.kind = Contacts.KIND_PHONE; - } else if (sOrganizationsTable.equals(table)) { - info.kind = Contacts.KIND_ORGANIZATION; - } else { - throw new IllegalArgumentException("unexpected table, " + table); - } - } - return info; - } finally { - cursor.close(); - } - } - - /** - * Returns the rank of the table-specific type, used when deciding which row - * should be primary when none are primary. The lower the rank the better the type. - * @param table supports "phones", "contact_methods" and "organizations" - * @param type the table-specific type from the TYPE column - * @return the rank of the table-specific type, the lower the better - */ - private int getRankOfType(String table, int type) { - if (table.equals(sPhonesTable)) { - switch (type) { - case Contacts.Phones.TYPE_MOBILE: return 0; - case Contacts.Phones.TYPE_WORK: return 1; - case Contacts.Phones.TYPE_HOME: return 2; - case Contacts.Phones.TYPE_PAGER: return 3; - case Contacts.Phones.TYPE_CUSTOM: return 4; - case Contacts.Phones.TYPE_OTHER: return 5; - case Contacts.Phones.TYPE_FAX_WORK: return 6; - case Contacts.Phones.TYPE_FAX_HOME: return 7; - default: return 1000; - } - } - - if (table.equals(sContactMethodsTable)) { - switch (type) { - case Contacts.ContactMethods.TYPE_HOME: return 0; - case Contacts.ContactMethods.TYPE_WORK: return 1; - case Contacts.ContactMethods.TYPE_CUSTOM: return 2; - case Contacts.ContactMethods.TYPE_OTHER: return 3; - default: return 1000; - } - } - - if (table.equals(sOrganizationsTable)) { - switch (type) { - case Organizations.TYPE_WORK: return 0; - case Organizations.TYPE_CUSTOM: return 1; - case Organizations.TYPE_OTHER: return 2; - default: return 1000; - } - } - - throw new IllegalArgumentException("unexpected table, " + table); - } - - /** - * Determines which of the rows in table for the personId should be picked as the primary - * row based on the rank of the row's type. - * @param kind the kind of contact - * @param personId used to limit the rows to those pertaining to this person - * @param itemId optional, a row to ignore - * @return the _id of the row that should be the new primary. Is null if there are no - * matching rows. - */ - private Long findNewPrimary(int kind, Long personId, Long itemId) { - final String table = kindToTable(kind); - if (personId == null) throw new IllegalArgumentException("personId must not be null"); - StringBuilder sb = new StringBuilder(); - sb.append("person="); - sb.append(personId); - if (itemId != null) { - sb.append(" and _id!="); - sb.append(itemId); - } - if (sContactMethodsTable.equals(table)) { - sb.append(" and "); - sb.append(ContactMethods.KIND); - sb.append("="); - sb.append(kind); - } - - Cursor cursor = getDatabase().query(table, ID_TYPE_PROJECTION, sb.toString(), - null, null, null, null); - try { - Long newPrimaryId = null; - int bestRank = -1; - while (cursor.moveToNext()) { - final int rank = getRankOfType(table, cursor.getInt(1)); - if (bestRank == -1 || rank < bestRank) { - newPrimaryId = cursor.getLong(0); - bestRank = rank; - } - } - return newPrimaryId; - } finally { - cursor.close(); - } - } - - private void setIsPrimary(int kind, long personId, long itemId) { - final String table = kindToTable(kind); - StringBuilder sb = new StringBuilder(); - sb.append("person="); - sb.append(personId); - - if (sContactMethodsTable.equals(table)) { - sb.append(" and "); - sb.append(ContactMethods.KIND); - sb.append("="); - sb.append(kind); - } - - final String where = sb.toString(); - getDatabase().execSQL( - "UPDATE " + table + " SET isprimary=(_id=" + itemId + ") WHERE " + where); - } - - /** - * Clears the isprimary flag for all rows other than the itemId. - * @param kind the kind of item - * @param personId used to limit the updates to rows pertaining to this person - * @param itemId which row to leave untouched - */ - private void clearOtherIsPrimary(int kind, Long personId, Long itemId) { - final String table = kindToTable(kind); - if (personId == null) throw new IllegalArgumentException("personId must not be null"); - StringBuilder sb = new StringBuilder(); - sb.append("person="); - sb.append(personId); - if (itemId != null) { - sb.append(" and _id!="); - sb.append(itemId); - } - if (sContactMethodsTable.equals(table)) { - sb.append(" and "); - sb.append(ContactMethods.KIND); - sb.append("="); - sb.append(kind); - } - - mValuesLocal.clear(); - mValuesLocal.put("isprimary", 0); - getDatabase().update(table, mValuesLocal, sb.toString(), null); - } - - /** - * Set the specified primary column for the person. This is used to make the people - * row reflect the isprimary flag in the people or contactmethods tables, which is - * authoritative. - * @param personId the person to modify - * @param column the name of the primary column (phone or email) - * @param primaryId the new value to write into the primary column - */ - private void updatePeoplePrimary(Long personId, String column, Long primaryId) { - mValuesLocal.clear(); - mValuesLocal.put(column, primaryId); - getDatabase().update(sPeopleTable, mValuesLocal, "_id=" + personId, null); - } - - private static String addIdToWhereClause(String id, String where) { - if (id != null) { - StringBuilder whereSb = new StringBuilder("_id="); - whereSb.append(id); - if (!TextUtils.isEmpty(where)) { - whereSb.append(" AND ("); - whereSb.append(where); - whereSb.append(')'); - } - return whereSb.toString(); - } else { - return where; - } - } - - private boolean queryGroupMembershipContainsStarred(long personId) { - // TODO: Part 1 of 2 part hack to work around a bug in reusing SQLiteStatements - SQLiteStatement mGroupsMembershipQuery = null; - - if (mGroupsMembershipQuery == null) { - String query = - "SELECT COUNT(*) FROM groups, groupmembership WHERE " - + sGroupsJoinString + " AND person=? AND groups.name=?"; - mGroupsMembershipQuery = getDatabase().compileStatement(query); - } - long result = DatabaseUtils.longForQuery(mGroupsMembershipQuery, - new String[]{String.valueOf(personId), Groups.GROUP_ANDROID_STARRED}); - - // TODO: Part 2 of 2 part hack to work around a bug in reusing SQLiteStatements - mGroupsMembershipQuery.close(); - - return result != 0; - } - - @Override - public boolean changeRequiresLocalSync(Uri uri) { - final int match = sURIMatcher.match(uri); - switch (match) { - // Changes to these URIs cannot cause syncable data to be changed, so don't - // bother trying to sync them. - case CALLS: - case CALLS_FILTER: - case CALLS_ID: - case PRESENCE: - case PRESENCE_ID: - case PEOPLE_UPDATE_CONTACT_TIME: - return false; - - default: - return true; - } - } - - @Override - protected Iterable<? extends AbstractTableMerger> getMergers() { - ArrayList<AbstractTableMerger> list = new ArrayList<AbstractTableMerger> (); - list.add(new PersonMerger()); - list.add(new GroupMerger()); - list.add(new PhotoMerger()); - return list; - } - - protected static String sPeopleTable = "people"; - protected static Uri sPeopleRawURL = Uri.parse("content://contacts/people/raw/"); - protected static String sDeletedPeopleTable = "_deleted_people"; - protected static Uri sDeletedPeopleURL = Uri.parse("content://contacts/deleted_people/"); - protected static String sGroupsTable = "groups"; - protected static String sSettingsTable = "settings"; - protected static Uri sGroupsURL = Uri.parse("content://contacts/groups/"); - protected static String sDeletedGroupsTable = "_deleted_groups"; - protected static Uri sDeletedGroupsURL = - Uri.parse("content://contacts/deleted_groups/"); - protected static String sPhonesTable = "phones"; - protected static String sOrganizationsTable = "organizations"; - protected static String sContactMethodsTable = "contact_methods"; - protected static String sGroupmembershipTable = "groupmembership"; - protected static String sPhotosTable = "photos"; - protected static Uri sPhotosURL = Uri.parse("content://contacts/photos/"); - protected static String sExtensionsTable = "extensions"; - protected static String sCallsTable = "calls"; - - protected class PersonMerger extends AbstractTableMerger - { - private ContentValues mValues = new ContentValues(); - Map<String, SQLiteCursor> mCursorMap = Maps.newHashMap(); - public PersonMerger() - { - super(getDatabase(), - sPeopleTable, sPeopleRawURL, sDeletedPeopleTable, sDeletedPeopleURL); - } - - @Override - protected void notifyChanges() { - // notify that a change has occurred. - getContext().getContentResolver().notifyChange(Contacts.CONTENT_URI, - null /* observer */, false /* do not sync to network */); - } - - @Override - public void insertRow(ContentProvider diffs, Cursor diffsCursor) { - final SQLiteDatabase db = getDatabase(); - - Long localPrimaryPhoneId = null; - Long localPrimaryEmailId = null; - Long localPrimaryOrganizationId = null; - - // Copy the person - mPeopleInserter.prepareForInsert(); - DatabaseUtils.cursorStringToInsertHelper(diffsCursor, People._SYNC_ID, mPeopleInserter, mIndexPeopleSyncId); - DatabaseUtils.cursorStringToInsertHelper(diffsCursor, People._SYNC_TIME, mPeopleInserter, mIndexPeopleSyncTime); - DatabaseUtils.cursorStringToInsertHelper(diffsCursor, People._SYNC_VERSION, mPeopleInserter, mIndexPeopleSyncVersion); - DatabaseUtils.cursorStringToInsertHelper(diffsCursor, People._SYNC_DIRTY, mPeopleInserter, mIndexPeopleSyncDirty); - DatabaseUtils.cursorStringToInsertHelper(diffsCursor, People._SYNC_ACCOUNT, mPeopleInserter, mIndexPeopleSyncAccount); - DatabaseUtils.cursorStringToInsertHelper(diffsCursor, People.NAME, mPeopleInserter, mIndexPeopleName); - DatabaseUtils.cursorStringToInsertHelper(diffsCursor, People.PHONETIC_NAME, mPeopleInserter, mIndexPeoplePhoneticName); - DatabaseUtils.cursorStringToInsertHelper(diffsCursor, People.NOTES, mPeopleInserter, mIndexPeopleNotes); - long localPersonID = mPeopleInserter.execute(); - - Cursor c; - final SQLiteDatabase diffsDb = ((ContactsProvider) diffs).getDatabase(); - long diffsPersonID = diffsCursor.getLong(diffsCursor.getColumnIndexOrThrow(People._ID)); - - // Copy the Photo info - c = doSubQuery(diffsDb, sPhotosTable, null, diffsPersonID, null); - try { - if (c.moveToNext()) { - mDb.delete(sPhotosTable, "person=" + localPersonID, null); - mPhotosInserter.prepareForInsert(); - DatabaseUtils.cursorStringToInsertHelper(c, Photos._SYNC_ID, - mPhotosInserter, mIndexPhotosSyncId); - DatabaseUtils.cursorStringToInsertHelper(c, Photos._SYNC_TIME, - mPhotosInserter, mIndexPhotosSyncTime); - DatabaseUtils.cursorStringToInsertHelper(c, Photos._SYNC_VERSION, - mPhotosInserter, mIndexPhotosSyncVersion); - DatabaseUtils.cursorStringToInsertHelper(c, Photos._SYNC_ACCOUNT, - mPhotosInserter, mIndexPhotosSyncAccount); - DatabaseUtils.cursorStringToInsertHelper(c, Photos.EXISTS_ON_SERVER, - mPhotosInserter, mIndexPhotosExistsOnServer); - mPhotosInserter.bind(mIndexPhotosSyncError, (String)null); - mPhotosInserter.bind(mIndexPhotosSyncDirty, 0); - mPhotosInserter.bind(mIndexPhotosPersonId, localPersonID); - mPhotosInserter.execute(); - } - } finally { - c.deactivate(); - } - - // Copy all phones - c = doSubQuery(diffsDb, sPhonesTable, null, diffsPersonID, sPhonesTable + "._id"); - if (c != null) { - Long newPrimaryId = null; - int bestRank = -1; - final int labelIndex = c.getColumnIndexOrThrow(Phones.LABEL); - final int typeIndex = c.getColumnIndexOrThrow(Phones.TYPE); - final int numberIndex = c.getColumnIndexOrThrow(Phones.NUMBER); - final int keyIndex = c.getColumnIndexOrThrow(Phones.NUMBER_KEY); - final int primaryIndex = c.getColumnIndexOrThrow(Phones.ISPRIMARY); - while(c.moveToNext()) { - final int type = c.getInt(typeIndex); - final int isPrimaryValue = c.getInt(primaryIndex); - mPhonesInserter.prepareForInsert(); - mPhonesInserter.bind(mIndexPhonesPersonId, localPersonID); - mPhonesInserter.bind(mIndexPhonesLabel, c.getString(labelIndex)); - mPhonesInserter.bind(mIndexPhonesType, type); - mPhonesInserter.bind(mIndexPhonesNumber, c.getString(numberIndex)); - mPhonesInserter.bind(mIndexPhonesNumberKey, c.getString(keyIndex)); - mPhonesInserter.bind(mIndexPhonesIsPrimary, isPrimaryValue); - long rowId = mPhonesInserter.execute(); - - if (isPrimaryValue != 0) { - if (localPrimaryPhoneId != null) { - throw new IllegalArgumentException( - "more than one phone was marked as primary, " - + DatabaseUtils.dumpCursorToString(c)); - } - localPrimaryPhoneId = rowId; - } - - if (localPrimaryPhoneId == null) { - final int rank = getRankOfType(sPhonesTable, type); - if (bestRank == -1 || rank < bestRank) { - newPrimaryId = rowId; - bestRank = rank; - } - } - } - c.deactivate(); - - if (localPrimaryPhoneId == null) { - localPrimaryPhoneId = newPrimaryId; - } - } - - // Copy all contact_methods - c = doSubQuery(diffsDb, sContactMethodsTable, null, diffsPersonID, - sContactMethodsTable + "._id"); - if (c != null) { - Long newPrimaryId = null; - int bestRank = -1; - final int labelIndex = c.getColumnIndexOrThrow(ContactMethods.LABEL); - final int kindIndex = c.getColumnIndexOrThrow(ContactMethods.KIND); - final int typeIndex = c.getColumnIndexOrThrow(ContactMethods.TYPE); - final int dataIndex = c.getColumnIndexOrThrow(ContactMethods.DATA); - final int auxDataIndex = c.getColumnIndexOrThrow(ContactMethods.AUX_DATA); - final int primaryIndex = c.getColumnIndexOrThrow(ContactMethods.ISPRIMARY); - while(c.moveToNext()) { - final int type = c.getInt(typeIndex); - final int kind = c.getInt(kindIndex); - final int isPrimaryValue = c.getInt(primaryIndex); - mContactMethodsInserter.prepareForInsert(); - mContactMethodsInserter.bind(mIndexContactMethodsPersonId, localPersonID); - mContactMethodsInserter.bind(mIndexContactMethodsLabel, c.getString(labelIndex)); - mContactMethodsInserter.bind(mIndexContactMethodsKind, kind); - mContactMethodsInserter.bind(mIndexContactMethodsType, type); - mContactMethodsInserter.bind(mIndexContactMethodsData, c.getString(dataIndex)); - mContactMethodsInserter.bind(mIndexContactMethodsAuxData, c.getString(auxDataIndex)); - mContactMethodsInserter.bind(mIndexContactMethodsIsPrimary, isPrimaryValue); - long rowId = mContactMethodsInserter.execute(); - if ((kind == Contacts.KIND_EMAIL) && (isPrimaryValue != 0)) { - if (localPrimaryEmailId != null) { - throw new IllegalArgumentException( - "more than one email was marked as primary, " - + DatabaseUtils.dumpCursorToString(c)); - } - localPrimaryEmailId = rowId; - } - - if (localPrimaryEmailId == null) { - final int rank = getRankOfType(sContactMethodsTable, type); - if (bestRank == -1 || rank < bestRank) { - newPrimaryId = rowId; - bestRank = rank; - } - } - } - c.deactivate(); - - if (localPrimaryEmailId == null) { - localPrimaryEmailId = newPrimaryId; - } - } - - // Copy all organizations - c = doSubQuery(diffsDb, sOrganizationsTable, null, diffsPersonID, - sOrganizationsTable + "._id"); - try { - Long newPrimaryId = null; - int bestRank = -1; - final int labelIndex = c.getColumnIndexOrThrow(Organizations.LABEL); - final int typeIndex = c.getColumnIndexOrThrow(Organizations.TYPE); - final int companyIndex = c.getColumnIndexOrThrow(Organizations.COMPANY); - final int titleIndex = c.getColumnIndexOrThrow(Organizations.TITLE); - final int primaryIndex = c.getColumnIndexOrThrow(Organizations.ISPRIMARY); - while(c.moveToNext()) { - final int type = c.getInt(typeIndex); - final int isPrimaryValue = c.getInt(primaryIndex); - mOrganizationsInserter.prepareForInsert(); - mOrganizationsInserter.bind(mIndexOrganizationsPersonId, localPersonID); - mOrganizationsInserter.bind(mIndexOrganizationsLabel, c.getString(labelIndex)); - mOrganizationsInserter.bind(mIndexOrganizationsType, type); - mOrganizationsInserter.bind(mIndexOrganizationsCompany, c.getString(companyIndex)); - mOrganizationsInserter.bind(mIndexOrganizationsTitle, c.getString(titleIndex)); - mOrganizationsInserter.bind(mIndexOrganizationsIsPrimary, isPrimaryValue); - long rowId = mOrganizationsInserter.execute(); - if (isPrimaryValue != 0) { - if (localPrimaryOrganizationId != null) { - throw new IllegalArgumentException( - "more than one organization was marked as primary, " - + DatabaseUtils.dumpCursorToString(c)); - } - localPrimaryOrganizationId = rowId; - } - - if (localPrimaryOrganizationId == null) { - final int rank = getRankOfType(sOrganizationsTable, type); - if (bestRank == -1 || rank < bestRank) { - newPrimaryId = rowId; - bestRank = rank; - } - } - } - - if (localPrimaryOrganizationId == null) { - localPrimaryOrganizationId = newPrimaryId; - } - } finally { - c.deactivate(); - } - - // Copy all groupmembership rows - c = doSubQuery(diffsDb, sGroupmembershipTable, null, diffsPersonID, - sGroupmembershipTable + "._id"); - try { - final int accountIndex = - c.getColumnIndexOrThrow(GroupMembership.GROUP_SYNC_ACCOUNT); - final int idIndex = c.getColumnIndexOrThrow(GroupMembership.GROUP_SYNC_ID); - while(c.moveToNext()) { - mGroupMembershipInserter.prepareForInsert(); - mGroupMembershipInserter.bind(mIndexGroupMembershipPersonId, localPersonID); - mGroupMembershipInserter.bind(mIndexGroupMembershipGroupSyncAccount, c.getString(accountIndex)); - mGroupMembershipInserter.bind(mIndexGroupMembershipGroupSyncId, c.getString(idIndex)); - mGroupMembershipInserter.execute(); - } - } finally { - c.deactivate(); - } - - // Copy all extensions rows - c = doSubQuery(diffsDb, sExtensionsTable, null, diffsPersonID, sExtensionsTable + "._id"); - try { - final int nameIndex = c.getColumnIndexOrThrow(Extensions.NAME); - final int valueIndex = c.getColumnIndexOrThrow(Extensions.VALUE); - while(c.moveToNext()) { - mExtensionsInserter.prepareForInsert(); - mExtensionsInserter.bind(mIndexExtensionsPersonId, localPersonID); - mExtensionsInserter.bind(mIndexExtensionsName, c.getString(nameIndex)); - mExtensionsInserter.bind(mIndexExtensionsValue, c.getString(valueIndex)); - mExtensionsInserter.execute(); - } - } finally { - c.deactivate(); - } - - // Update the _SYNC_DIRTY flag of the person. We have to do this - // after inserting since the updated of the phones, contact - // methods and organizations will fire a sql trigger that will - // cause this flag to be set. - mValues.clear(); - mValues.put(People._SYNC_DIRTY, 0); - mValues.put(People.PRIMARY_PHONE_ID, localPrimaryPhoneId); - mValues.put(People.PRIMARY_EMAIL_ID, localPrimaryEmailId); - mValues.put(People.PRIMARY_ORGANIZATION_ID, localPrimaryOrganizationId); - final boolean isStarred = queryGroupMembershipContainsStarred(localPersonID); - mValues.put(People.STARRED, isStarred ? 1 : 0); - db.update(mTable, mValues, People._ID + '=' + localPersonID, null); - } - - @Override - public void updateRow(long localPersonID, ContentProvider diffs, Cursor diffsCursor) { - updateOrResolveRow(localPersonID, null, diffs, diffsCursor, false); - } - - @Override - public void resolveRow(long localPersonID, String syncID, - ContentProvider diffs, Cursor diffsCursor) { - updateOrResolveRow(localPersonID, syncID, diffs, diffsCursor, true); - } - - protected void updateOrResolveRow(long localPersonID, String syncID, - ContentProvider diffs, Cursor diffsCursor, boolean conflicts) { - final SQLiteDatabase db = getDatabase(); - // The local version of localPersonId's record has changed. This - // person also has a changed record in the diffs. Merge the changes - // in the following way: - // - if any fields in the people table changed use the server's - // version - // - for phones, emails, addresses, compute the join of all unique - // subrecords. If any of the subrecords has changes in both - // places then choose the server version of the subrecord - // - // Limitation: deletes of phones, emails, or addresses are ignored - // when the record has changed on both the client and the server - - long diffsPersonID = diffsCursor.getLong(diffsCursor.getColumnIndexOrThrow("_id")); - - // Join the server phones, organizations, and contact_methods with the local ones. - // - Add locally any that exist only on the server. - // - If the row conflicts, delete locally any that exist only on the client. - // - If the row doesn't conflict, ignore any that exist only on the client. - // - Update any that exist in both places. - - Map<Integer, Long> primaryLocal = new HashMap<Integer, Long>(); - Map<Integer, Long> primaryDiffs = new HashMap<Integer, Long>(); - - Cursor cRemote; - Cursor cLocal; - - // Phones - cRemote = null; - cLocal = null; - final SQLiteDatabase diffsDb = ((ContactsProvider) diffs).getDatabase(); - try { - cLocal = doSubQuery(db, sPhonesTable, null, localPersonID, sPhonesKeyOrderBy); - cRemote = doSubQuery(diffsDb, sPhonesTable, - null, diffsPersonID, sPhonesKeyOrderBy); - - final int idColLocal = cLocal.getColumnIndexOrThrow(Phones._ID); - final int isPrimaryColLocal = cLocal.getColumnIndexOrThrow(Phones.ISPRIMARY); - final int isPrimaryColRemote = cRemote.getColumnIndexOrThrow(Phones.ISPRIMARY); - - CursorJoiner joiner = - new CursorJoiner(cLocal, sPhonesKeyColumns, cRemote, sPhonesKeyColumns); - for (CursorJoiner.Result joinResult : joiner) { - switch(joinResult) { - case LEFT: - if (!conflicts) { - db.delete(sPhonesTable, - Phones._ID + "=" + cLocal.getLong(idColLocal), null); - } else { - if (cLocal.getLong(isPrimaryColLocal) != 0) { - savePrimaryId(primaryLocal, Contacts.KIND_PHONE, - cLocal.getLong(idColLocal)); - } - } - break; - - case RIGHT: - case BOTH: - mValues.clear(); - DatabaseUtils.cursorIntToContentValues( - cRemote, Phones.TYPE, mValues); - DatabaseUtils.cursorStringToContentValues( - cRemote, Phones.LABEL, mValues); - DatabaseUtils.cursorStringToContentValues( - cRemote, Phones.NUMBER, mValues); - DatabaseUtils.cursorStringToContentValues( - cRemote, Phones.NUMBER_KEY, mValues); - DatabaseUtils.cursorIntToContentValues( - cRemote, Phones.ISPRIMARY, mValues); - - long localId; - if (joinResult == CursorJoiner.Result.RIGHT) { - mValues.put(Phones.PERSON_ID, localPersonID); - localId = mPhonesInserter.insert(mValues); - } else { - localId = cLocal.getLong(idColLocal); - db.update(sPhonesTable, mValues, "_id =" + localId, null); - } - if (cRemote.getLong(isPrimaryColRemote) != 0) { - savePrimaryId(primaryDiffs, Contacts.KIND_PHONE, localId); - } - break; - } - } - } finally { - if (cRemote != null) cRemote.deactivate(); - if (cLocal != null) cLocal.deactivate(); - } - - // Contact methods - cRemote = null; - cLocal = null; - try { - cLocal = doSubQuery(db, - sContactMethodsTable, null, localPersonID, sContactMethodsKeyOrderBy); - cRemote = doSubQuery(diffsDb, - sContactMethodsTable, null, diffsPersonID, sContactMethodsKeyOrderBy); - - final int idColLocal = cLocal.getColumnIndexOrThrow(ContactMethods._ID); - final int kindColLocal = cLocal.getColumnIndexOrThrow(ContactMethods.KIND); - final int kindColRemote = cRemote.getColumnIndexOrThrow(ContactMethods.KIND); - final int isPrimaryColLocal = - cLocal.getColumnIndexOrThrow(ContactMethods.ISPRIMARY); - final int isPrimaryColRemote = - cRemote.getColumnIndexOrThrow(ContactMethods.ISPRIMARY); - - CursorJoiner joiner = new CursorJoiner( - cLocal, sContactMethodsKeyColumns, cRemote, sContactMethodsKeyColumns); - for (CursorJoiner.Result joinResult : joiner) { - switch(joinResult) { - case LEFT: - if (!conflicts) { - db.delete(sContactMethodsTable, ContactMethods._ID + "=" - + cLocal.getLong(idColLocal), null); - } else { - if (cLocal.getLong(isPrimaryColLocal) != 0) { - savePrimaryId(primaryLocal, cLocal.getInt(kindColLocal), - cLocal.getLong(idColLocal)); - } - } - break; - - case RIGHT: - case BOTH: - mValues.clear(); - DatabaseUtils.cursorStringToContentValues(cRemote, - ContactMethods.LABEL, mValues); - DatabaseUtils.cursorIntToContentValues(cRemote, - ContactMethods.TYPE, mValues); - DatabaseUtils.cursorIntToContentValues(cRemote, - ContactMethods.KIND, mValues); - DatabaseUtils.cursorStringToContentValues(cRemote, - ContactMethods.DATA, mValues); - DatabaseUtils.cursorStringToContentValues(cRemote, - ContactMethods.AUX_DATA, mValues); - DatabaseUtils.cursorIntToContentValues(cRemote, - ContactMethods.ISPRIMARY, mValues); - - long localId; - if (joinResult == CursorJoiner.Result.RIGHT) { - mValues.put(ContactMethods.PERSON_ID, localPersonID); - localId = mContactMethodsInserter.insert(mValues); - } else { - localId = cLocal.getLong(idColLocal); - db.update(sContactMethodsTable, mValues, "_id =" + localId, null); - } - if (cRemote.getLong(isPrimaryColRemote) != 0) { - savePrimaryId(primaryDiffs, cRemote.getInt(kindColRemote), localId); - } - break; - } - } - } finally { - if (cRemote != null) cRemote.deactivate(); - if (cLocal != null) cLocal.deactivate(); - } - - // Organizations - cRemote = null; - cLocal = null; - try { - cLocal = doSubQuery(db, - sOrganizationsTable, null, localPersonID, sOrganizationsKeyOrderBy); - cRemote = doSubQuery(diffsDb, - sOrganizationsTable, null, diffsPersonID, sOrganizationsKeyOrderBy); - - final int idColLocal = cLocal.getColumnIndexOrThrow(Organizations._ID); - final int isPrimaryColLocal = - cLocal.getColumnIndexOrThrow(ContactMethods.ISPRIMARY); - final int isPrimaryColRemote = - cRemote.getColumnIndexOrThrow(ContactMethods.ISPRIMARY); - CursorJoiner joiner = new CursorJoiner( - cLocal, sOrganizationsKeyColumns, cRemote, sOrganizationsKeyColumns); - for (CursorJoiner.Result joinResult : joiner) { - switch(joinResult) { - case LEFT: - if (!conflicts) { - db.delete(sOrganizationsTable, - Phones._ID + "=" + cLocal.getLong(idColLocal), null); - } else { - if (cLocal.getLong(isPrimaryColLocal) != 0) { - savePrimaryId(primaryLocal, Contacts.KIND_ORGANIZATION, - cLocal.getLong(idColLocal)); - } - } - break; - - case RIGHT: - case BOTH: - mValues.clear(); - DatabaseUtils.cursorStringToContentValues(cRemote, - Organizations.LABEL, mValues); - DatabaseUtils.cursorIntToContentValues(cRemote, - Organizations.TYPE, mValues); - DatabaseUtils.cursorStringToContentValues(cRemote, - Organizations.COMPANY, mValues); - DatabaseUtils.cursorStringToContentValues(cRemote, - Organizations.TITLE, mValues); - DatabaseUtils.cursorIntToContentValues(cRemote, - Organizations.ISPRIMARY, mValues); - long localId; - if (joinResult == CursorJoiner.Result.RIGHT) { - mValues.put(Organizations.PERSON_ID, localPersonID); - localId = mOrganizationsInserter.insert(mValues); - } else { - localId = cLocal.getLong(idColLocal); - db.update(sOrganizationsTable, mValues, - "_id =" + localId, null /* whereArgs */); - } - if (cRemote.getLong(isPrimaryColRemote) != 0) { - savePrimaryId(primaryDiffs, Contacts.KIND_ORGANIZATION, localId); - } - break; - } - } - } finally { - if (cRemote != null) cRemote.deactivate(); - if (cLocal != null) cLocal.deactivate(); - } - - // Groupmembership - cRemote = null; - cLocal = null; - try { - cLocal = doSubQuery(db, - sGroupmembershipTable, null, localPersonID, sGroupmembershipKeyOrderBy); - cRemote = doSubQuery(diffsDb, - sGroupmembershipTable, null, diffsPersonID, sGroupmembershipKeyOrderBy); - - final int idColLocal = cLocal.getColumnIndexOrThrow(GroupMembership._ID); - CursorJoiner joiner = new CursorJoiner( - cLocal, sGroupmembershipKeyColumns, cRemote, sGroupmembershipKeyColumns); - for (CursorJoiner.Result joinResult : joiner) { - switch(joinResult) { - case LEFT: - if (!conflicts) { - db.delete(sGroupmembershipTable, - Phones._ID + "=" + cLocal.getLong(idColLocal), null); - } - break; - - case RIGHT: - case BOTH: - mValues.clear(); - DatabaseUtils.cursorStringToContentValues(cRemote, - GroupMembership.GROUP_SYNC_ACCOUNT, mValues); - DatabaseUtils.cursorStringToContentValues(cRemote, - GroupMembership.GROUP_SYNC_ID, mValues); - if (joinResult == CursorJoiner.Result.RIGHT) { - mValues.put(GroupMembership.PERSON_ID, localPersonID); - mGroupMembershipInserter.insert(mValues); - } else { - db.update(sGroupmembershipTable, mValues, - "_id =" + cLocal.getLong(idColLocal), null /* whereArgs */); - } - break; - } - } - } finally { - if (cRemote != null) cRemote.deactivate(); - if (cLocal != null) cLocal.deactivate(); - } - - // Extensions - cRemote = null; - cLocal = null; - try { - cLocal = doSubQuery(db, - sExtensionsTable, null, localPersonID, Extensions.NAME); - cRemote = doSubQuery(diffsDb, - sExtensionsTable, null, diffsPersonID, Extensions.NAME); - - final int idColLocal = cLocal.getColumnIndexOrThrow(Extensions._ID); - CursorJoiner joiner = new CursorJoiner( - cLocal, sExtensionsKeyColumns, cRemote, sExtensionsKeyColumns); - for (CursorJoiner.Result joinResult : joiner) { - switch(joinResult) { - case LEFT: - if (!conflicts) { - db.delete(sExtensionsTable, - Phones._ID + "=" + cLocal.getLong(idColLocal), null); - } - break; - - case RIGHT: - case BOTH: - mValues.clear(); - DatabaseUtils.cursorStringToContentValues(cRemote, - Extensions.NAME, mValues); - DatabaseUtils.cursorStringToContentValues(cRemote, - Extensions.VALUE, mValues); - if (joinResult == CursorJoiner.Result.RIGHT) { - mValues.put(Extensions.PERSON_ID, localPersonID); - mExtensionsInserter.insert(mValues); - } else { - db.update(sExtensionsTable, mValues, - "_id =" + cLocal.getLong(idColLocal), null /* whereArgs */); - } - break; - } - } - } finally { - if (cRemote != null) cRemote.deactivate(); - if (cLocal != null) cLocal.deactivate(); - } - - // Copy the Photo's server id and account so that the merger will find it - cRemote = doSubQuery(diffsDb, sPhotosTable, null, diffsPersonID, null); - try { - if(cRemote.moveToNext()) { - mValues.clear(); - DatabaseUtils.cursorStringToContentValues(cRemote, Photos._SYNC_ID, mValues); - DatabaseUtils.cursorStringToContentValues(cRemote, Photos._SYNC_ACCOUNT, mValues); - db.update(sPhotosTable, mValues, Photos.PERSON_ID + '=' + localPersonID, null); - } - } finally { - cRemote.deactivate(); - } - - // make sure there is exactly one primary set for each of these types - Long primaryPhoneId = setSinglePrimary( - primaryDiffs, primaryLocal, localPersonID, Contacts.KIND_PHONE); - - Long primaryEmailId = setSinglePrimary( - primaryDiffs, primaryLocal, localPersonID, Contacts.KIND_EMAIL); - - Long primaryOrganizationId = setSinglePrimary( - primaryDiffs, primaryLocal, localPersonID, Contacts.KIND_ORGANIZATION); - - setSinglePrimary(primaryDiffs, primaryLocal, localPersonID, Contacts.KIND_IM); - - setSinglePrimary(primaryDiffs, primaryLocal, localPersonID, Contacts.KIND_POSTAL); - - // Update the person - mValues.clear(); - DatabaseUtils.cursorStringToContentValues(diffsCursor, People._SYNC_ID, mValues); - DatabaseUtils.cursorStringToContentValues(diffsCursor, People._SYNC_TIME, mValues); - DatabaseUtils.cursorStringToContentValues(diffsCursor, People._SYNC_VERSION, mValues); - DatabaseUtils.cursorStringToContentValues(diffsCursor, People._SYNC_ACCOUNT, mValues); - DatabaseUtils.cursorStringToContentValues(diffsCursor, People.NAME, mValues); - DatabaseUtils.cursorStringToContentValues(diffsCursor, People.PHONETIC_NAME, mValues); - DatabaseUtils.cursorStringToContentValues(diffsCursor, People.NOTES, mValues); - mValues.put(People.PRIMARY_PHONE_ID, primaryPhoneId); - mValues.put(People.PRIMARY_EMAIL_ID, primaryEmailId); - mValues.put(People.PRIMARY_ORGANIZATION_ID, primaryOrganizationId); - final boolean isStarred = queryGroupMembershipContainsStarred(localPersonID); - mValues.put(People.STARRED, isStarred ? 1 : 0); - mValues.put(People._SYNC_DIRTY, conflicts ? 1 : 0); - db.update(mTable, mValues, People._ID + '=' + localPersonID, null); - } - - private void savePrimaryId(Map<Integer, Long> primaryDiffs, Integer kind, long localId) { - if (primaryDiffs.containsKey(kind)) { - throw new IllegalArgumentException("more than one of kind " - + kind + " was marked as primary"); - } - primaryDiffs.put(kind, localId); - } - - private Long setSinglePrimary( - Map<Integer, Long> diffsMap, - Map<Integer, Long> localMap, - long localPersonID, int kind) { - Long primaryId = diffsMap.containsKey(kind) ? diffsMap.get(kind) : null; - if (primaryId == null) { - primaryId = localMap.containsKey(kind) ? localMap.get(kind) : null; - } - if (primaryId == null) { - primaryId = findNewPrimary(kind, localPersonID, null); - } - clearOtherIsPrimary(kind, localPersonID, primaryId); - return primaryId; - } - - /** - * Returns a cursor on the specified table that selects rows where - * the "person" column is equal to the personId parameter. The cursor - * is also saved and may be returned in future calls where db and table - * parameter are the same. In that case the projection and orderBy parameters - * are ignored, so one must take care to not change those parameters across - * multiple calls to the same db/table. - * <p> - * Since the cursor may be saced by this call, the caller must be sure to not - * close the cursor, though they still must deactivate it when they are done - * with it. - */ - private Cursor doSubQuery(SQLiteDatabase db, String table, String[] projection, - long personId, String orderBy) { - final String[] selectArgs = new String[]{Long.toString(personId)}; - final String key = (db == getDatabase() ? "local_" : "remote_") + table; - SQLiteCursor cursor = mCursorMap.get(key); - - // don't use the cached cursor if it is from a different DB - if (cursor != null && cursor.getDatabase() != db) { - cursor.close(); - cursor = null; - } - - // If we can't find a cached cursor then create a new one and add it to the cache. - // Otherwise just change the selection arguments and requery it. - if (cursor == null) { - cursor = (SQLiteCursor)db.query(table, projection, "person=?", selectArgs, - null, null, orderBy); - mCursorMap.put(key, cursor); - } else { - cursor.setSelectionArguments(selectArgs); - cursor.requery(); - } - return cursor; - } - } - - protected class GroupMerger extends AbstractTableMerger { - private ContentValues mValues = new ContentValues(); - - private static final String UNSYNCED_GROUP_BY_NAME_WHERE_CLAUSE = - Groups._SYNC_ID + " is null AND " - + Groups._SYNC_ACCOUNT + " is null AND " - + Groups.NAME + "=?"; - - private static final String UNSYNCED_GROUP_BY_SYSTEM_ID_WHERE_CLAUSE = - Groups._SYNC_ID + " is null AND " - + Groups._SYNC_ACCOUNT + " is null AND " - + Groups.SYSTEM_ID + "=?"; - - public GroupMerger() - { - super(getDatabase(), sGroupsTable, sGroupsURL, sDeletedGroupsTable, sDeletedGroupsURL); - } - - @Override - protected void notifyChanges() { - // notify that a change has occurred. - getContext().getContentResolver().notifyChange(Contacts.CONTENT_URI, - null /* observer */, false /* do not sync to network */); - } - - @Override - public void insertRow(ContentProvider diffs, Cursor cursor) { - // if an unsynced group with this name already exists then update it, otherwise - // insert a new group - mValues.clear(); - DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_ID, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_TIME, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_VERSION, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_ACCOUNT, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Groups.NAME, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Groups.NOTES, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Groups.SYSTEM_ID, mValues); - mValues.put(Groups._SYNC_DIRTY, 0); - - final String systemId = mValues.getAsString(Groups.SYSTEM_ID); - boolean rowUpdated = false; - if (TextUtils.isEmpty(systemId)) { - rowUpdated = getDatabase().update(mTable, mValues, - UNSYNCED_GROUP_BY_NAME_WHERE_CLAUSE, - new String[]{mValues.getAsString(Groups.NAME)}) > 0; - } else { - rowUpdated = getDatabase().update(mTable, mValues, - UNSYNCED_GROUP_BY_SYSTEM_ID_WHERE_CLAUSE, - new String[]{systemId}) > 0; - } - if (!rowUpdated) { - mGroupsInserter.insert(mValues); - } else { - // We may have just synced the metadata for a groups we previously marked for - // syncing. - final ContentResolver cr = getContext().getContentResolver(); - final String account = mValues.getAsString(Groups._SYNC_ACCOUNT); - onLocalChangesForAccount(cr, account, false); - } - - String oldName = null; - String newName = cursor.getString(cursor.getColumnIndexOrThrow(Groups.NAME)); - String account = cursor.getString(cursor.getColumnIndexOrThrow(Groups._SYNC_ACCOUNT)); - String syncId = cursor.getString(cursor.getColumnIndexOrThrow(Groups._SYNC_ID)); - // this must come after the insert, otherwise the join won't work - fixupPeopleStarredOnGroupRename(oldName, newName, account, syncId); - } - - @Override - public void updateRow(long localId, ContentProvider diffs, Cursor diffsCursor) { - updateOrResolveRow(localId, null, diffs, diffsCursor, false); - } - - @Override - public void resolveRow(long localId, String syncID, - ContentProvider diffs, Cursor diffsCursor) { - updateOrResolveRow(localId, syncID, diffs, diffsCursor, true); - } - - protected void updateOrResolveRow(long localRowId, String syncID, - ContentProvider diffs, Cursor cursor, boolean conflicts) { - final SQLiteDatabase db = getDatabase(); - - String oldName = DatabaseUtils.stringForQuery(db, - "select name from groups where _id=" + localRowId, null); - String newName = cursor.getString(cursor.getColumnIndexOrThrow(Groups.NAME)); - String account = cursor.getString(cursor.getColumnIndexOrThrow(Groups._SYNC_ACCOUNT)); - String syncId = cursor.getString(cursor.getColumnIndexOrThrow(Groups._SYNC_ID)); - // this can come before or after the delete - fixupPeopleStarredOnGroupRename(oldName, newName, account, syncId); - - mValues.clear(); - DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_ID, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_TIME, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_VERSION, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Groups._SYNC_ACCOUNT, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Groups.NAME, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Groups.NOTES, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Groups.SYSTEM_ID, mValues); - mValues.put(Groups._SYNC_DIRTY, 0); - db.update(mTable, mValues, Groups._ID + '=' + localRowId, null); - } - - @Override - public void deleteRow(Cursor cursor) { - // we have to read this row from the DB since the projection that is used - // by cursor doesn't necessarily contain the columns we need - Cursor c = getDatabase().query(sGroupsTable, null, - "_id=" + cursor.getLong(cursor.getColumnIndexOrThrow(Groups._ID)), - null, null, null, null); - try { - c.moveToNext(); - String oldName = c.getString(c.getColumnIndexOrThrow(Groups.NAME)); - String newName = null; - String account = c.getString(c.getColumnIndexOrThrow(Groups._SYNC_ACCOUNT)); - String syncId = c.getString(c.getColumnIndexOrThrow(Groups._SYNC_ID)); - String systemId = c.getString(c.getColumnIndexOrThrow(Groups.SYSTEM_ID)); - if (!TextUtils.isEmpty(systemId)) { - // We don't support deleting of system groups, but due to a server bug they - // occasionally get sent. Ignore the delete. - Log.w(TAG, "ignoring a delete for a system group: " + - DatabaseUtils.dumpCurrentRowToString(c)); - cursor.moveToNext(); - return; - } - - // this must come before the delete, since the join won't work once this row is gone - fixupPeopleStarredOnGroupRename(oldName, newName, account, syncId); - } finally { - c.close(); - } - - cursor.deleteRow(); - } - } - - protected class PhotoMerger extends AbstractTableMerger { - private ContentValues mValues = new ContentValues(); - - public PhotoMerger() { - super(getDatabase(), sPhotosTable, sPhotosURL, null, null); - } - - @Override - protected void notifyChanges() { - // notify that a change has occurred. - getContext().getContentResolver().notifyChange(Contacts.CONTENT_URI, - null /* observer */, false /* do not sync to network */); - } - - @Override - public void insertRow(ContentProvider diffs, Cursor cursor) { - // This photo may correspond to a contact that is in the delete table. If so then - // ignore this insert. - String syncId = cursor.getString(cursor.getColumnIndexOrThrow(Photos._SYNC_ID)); - boolean contactIsDeleted = DatabaseUtils.longForQuery(getDatabase(), - "select count(*) from _deleted_people where _sync_id=?", - new String[]{syncId}) > 0; - if (contactIsDeleted) { - return; - } - - throw new UnsupportedOperationException( - "the photo row is inserted by PersonMerger.insertRow"); - } - - @Override - public void updateRow(long localId, ContentProvider diffs, Cursor diffsCursor) { - updateOrResolveRow(localId, null, diffs, diffsCursor, false); - } - - @Override - public void resolveRow(long localId, String syncID, - ContentProvider diffs, Cursor diffsCursor) { - updateOrResolveRow(localId, syncID, diffs, diffsCursor, true); - } - - protected void updateOrResolveRow(long localRowId, String syncID, - ContentProvider diffs, Cursor cursor, boolean conflicts) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "PhotoMerger.updateOrResolveRow: localRowId " + localRowId - + ", syncId " + syncID + ", conflicts " + conflicts - + ", server row " + DatabaseUtils.dumpCurrentRowToString(cursor)); - } - mValues.clear(); - DatabaseUtils.cursorStringToContentValues(cursor, Photos._SYNC_TIME, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Photos._SYNC_VERSION, mValues); - DatabaseUtils.cursorStringToContentValues(cursor, Photos.EXISTS_ON_SERVER, mValues); - // reset the error field to allow the phone to attempt to redownload the photo. - mValues.put(Photos.SYNC_ERROR, (String)null); - - // If the photo didn't change locally and the server doesn't have a photo for this - // contact then delete the local photo. - long syncDirty = DatabaseUtils.longForQuery(getDatabase(), - "SELECT _sync_dirty FROM photos WHERE _id=" + localRowId - + " UNION SELECT 0 AS _sync_dirty ORDER BY _sync_dirty DESC LIMIT 1", - null); - if (syncDirty == 0) { - if (mValues.getAsInteger(Photos.EXISTS_ON_SERVER) == 0) { - mValues.put(Photos.DATA, (String)null); - mValues.put(Photos.LOCAL_VERSION, mValues.getAsString(Photos.LOCAL_VERSION)); - } - // if it does exist on the server then we will attempt to download it later - } - // if it does conflict then we will send the client version of the photo to - // the server later. That will trigger a new sync of the photo data which will - // cause this method to be called again, at which time the row will no longer - // conflict. We will then download the photo we just sent to the server and - // set the LOCAL_VERSION to match the data we just downloaded. - - getDatabase().update(mTable, mValues, Photos._ID + '=' + localRowId, null); - } - - @Override - public void deleteRow(Cursor cursor) { - // this row is never deleted explicitly, instead it is deleted by a trigger on - // the people table - cursor.moveToNext(); - } - } - - private static final String TAG = "ContactsProvider"; - - /* package private */ static final String DATABASE_NAME = "contacts.db"; - /* package private */ static final int DATABASE_VERSION = 80; - - protected static final String CONTACTS_AUTHORITY = "contacts"; - protected static final String CALL_LOG_AUTHORITY = "call_log"; - - private static final int PEOPLE_BASE = 0; - private static final int PEOPLE = PEOPLE_BASE; - private static final int PEOPLE_FILTER = PEOPLE_BASE + 1; - private static final int PEOPLE_ID = PEOPLE_BASE + 2; - private static final int PEOPLE_PHONES = PEOPLE_BASE + 3; - private static final int PEOPLE_PHONES_ID = PEOPLE_BASE + 4; - private static final int PEOPLE_CONTACTMETHODS = PEOPLE_BASE + 5; - private static final int PEOPLE_CONTACTMETHODS_ID = PEOPLE_BASE + 6; - private static final int PEOPLE_RAW = PEOPLE_BASE + 7; - private static final int PEOPLE_WITH_PHONES_FILTER = PEOPLE_BASE + 8; - private static final int PEOPLE_STREQUENT = PEOPLE_BASE + 9; - private static final int PEOPLE_STREQUENT_FILTER = PEOPLE_BASE + 10; - private static final int PEOPLE_ORGANIZATIONS = PEOPLE_BASE + 11; - private static final int PEOPLE_ORGANIZATIONS_ID = PEOPLE_BASE + 12; - private static final int PEOPLE_GROUPMEMBERSHIP = PEOPLE_BASE + 13; - private static final int PEOPLE_GROUPMEMBERSHIP_ID = PEOPLE_BASE + 14; - private static final int PEOPLE_PHOTO = PEOPLE_BASE + 15; - private static final int PEOPLE_EXTENSIONS = PEOPLE_BASE + 16; - private static final int PEOPLE_EXTENSIONS_ID = PEOPLE_BASE + 17; - private static final int PEOPLE_CONTACTMETHODS_WITH_PRESENCE = PEOPLE_BASE + 18; - private static final int PEOPLE_OWNER = PEOPLE_BASE + 19; - private static final int PEOPLE_UPDATE_CONTACT_TIME = PEOPLE_BASE + 20; - - private static final int DELETED_BASE = 1000; - private static final int DELETED_PEOPLE = DELETED_BASE; - private static final int DELETED_GROUPS = DELETED_BASE + 1; - - private static final int PHONES_BASE = 2000; - private static final int PHONES = PHONES_BASE; - private static final int PHONES_ID = PHONES_BASE + 1; - private static final int PHONES_FILTER = PHONES_BASE + 2; - private static final int PHONES_FILTER_NAME = PHONES_BASE + 3; - private static final int PHONES_MOBILE_FILTER_NAME = PHONES_BASE + 4; - - private static final int CONTACTMETHODS_BASE = 3000; - private static final int CONTACTMETHODS = CONTACTMETHODS_BASE; - private static final int CONTACTMETHODS_ID = CONTACTMETHODS_BASE + 1; - private static final int CONTACTMETHODS_EMAIL = CONTACTMETHODS_BASE + 2; - private static final int CONTACTMETHODS_EMAIL_FILTER = CONTACTMETHODS_BASE + 3; - private static final int CONTACTMETHODS_WITH_PRESENCE = CONTACTMETHODS_BASE + 4; - - private static final int CALLS_BASE = 4000; - private static final int CALLS = CALLS_BASE; - private static final int CALLS_ID = CALLS_BASE + 1; - private static final int CALLS_FILTER = CALLS_BASE + 2; - - private static final int PRESENCE_BASE = 5000; - private static final int PRESENCE = PRESENCE_BASE; - private static final int PRESENCE_ID = PRESENCE_BASE + 1; - - private static final int ORGANIZATIONS_BASE = 6000; - private static final int ORGANIZATIONS = ORGANIZATIONS_BASE; - private static final int ORGANIZATIONS_ID = ORGANIZATIONS_BASE + 1; - - private static final int VOICE_DIALER_TIMESTAMP = 7000; - private static final int SEARCH_SUGGESTIONS = 7001; - - private static final int GROUPS_BASE = 8000; - private static final int GROUPS = GROUPS_BASE; - private static final int GROUPS_ID = GROUPS_BASE + 2; - private static final int GROUP_NAME_MEMBERS = GROUPS_BASE + 3; - private static final int GROUP_NAME_MEMBERS_FILTER = GROUPS_BASE + 4; - private static final int GROUP_SYSTEM_ID_MEMBERS = GROUPS_BASE + 5; - private static final int GROUP_SYSTEM_ID_MEMBERS_FILTER = GROUPS_BASE + 6; - - private static final int GROUPMEMBERSHIP_BASE = 9000; - private static final int GROUPMEMBERSHIP = GROUPMEMBERSHIP_BASE; - private static final int GROUPMEMBERSHIP_ID = GROUPMEMBERSHIP_BASE + 2; - private static final int GROUPMEMBERSHIP_RAW = GROUPMEMBERSHIP_BASE + 3; - - private static final int PHOTOS_BASE = 10000; - private static final int PHOTOS = PHOTOS_BASE; - private static final int PHOTOS_ID = PHOTOS_BASE + 1; - - private static final int EXTENSIONS_BASE = 11000; - private static final int EXTENSIONS = EXTENSIONS_BASE; - private static final int EXTENSIONS_ID = EXTENSIONS_BASE + 2; - - private static final int SETTINGS = 12000; - - private static final int LIVE_FOLDERS_BASE = 13000; - private static final int LIVE_FOLDERS_PEOPLE = LIVE_FOLDERS_BASE + 1; - private static final int LIVE_FOLDERS_PEOPLE_GROUP_NAME = LIVE_FOLDERS_BASE + 2; - private static final int LIVE_FOLDERS_PEOPLE_WITH_PHONES = LIVE_FOLDERS_BASE + 3; - private static final int LIVE_FOLDERS_PEOPLE_FAVORITES = LIVE_FOLDERS_BASE + 4; - - private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); - - private static final HashMap<String, String> sGroupsProjectionMap; - private static final HashMap<String, String> sPeopleProjectionMap; - /** Used to force items to the top of a times_contacted list */ - private static final HashMap<String, String> sPeopleWithMaxTimesContactedProjectionMap; - private static final HashMap<String, String> sCallsProjectionMap; - private static final HashMap<String, String> sPhonesProjectionMap; - private static final HashMap<String, String> sContactMethodsProjectionMap; - private static final HashMap<String, String> sContactMethodsWithPresenceProjectionMap; - private static final HashMap<String, String> sPresenceProjectionMap; - private static final HashMap<String, String> sEmailSearchProjectionMap; - private static final HashMap<String, String> sOrganizationsProjectionMap; - private static final HashMap<String, String> sSearchSuggestionsProjectionMap; - private static final HashMap<String, String> sGroupMembershipProjectionMap; - private static final HashMap<String, String> sPhotosProjectionMap; - private static final HashMap<String, String> sExtensionsProjectionMap; - private static final HashMap<String, String> sLiveFoldersProjectionMap; - - private static final String sPhonesKeyOrderBy; - private static final String sContactMethodsKeyOrderBy; - private static final String sOrganizationsKeyOrderBy; - private static final String sGroupmembershipKeyOrderBy; - - private static final String DISPLAY_NAME_SQL - = "(CASE WHEN (name IS NOT NULL AND name != '') " - + "THEN name " - + "ELSE " - + "(CASE WHEN primary_organization is NOT NULL THEN " - + "(SELECT company FROM organizations WHERE " - + "organizations._id = primary_organization) " - + "ELSE " - + "(CASE WHEN primary_phone IS NOT NULL THEN " - +"(SELECT number FROM phones WHERE phones._id = primary_phone) " - + "ELSE " - + "(CASE WHEN primary_email IS NOT NULL THEN " - + "(SELECT data FROM contact_methods WHERE " - + "contact_methods._id = primary_email) " - + "ELSE " - + "null " - + "END) " - + "END) " - + "END) " - + "END) "; - - private static final String[] sPhonesKeyColumns; - private static final String[] sContactMethodsKeyColumns; - private static final String[] sOrganizationsKeyColumns; - private static final String[] sGroupmembershipKeyColumns; - private static final String[] sExtensionsKeyColumns; - - static private String buildOrderBy(String table, String... columns) { - StringBuilder sb = null; - for (String column : columns) { - if (sb == null) { - sb = new StringBuilder(); - } else { - sb.append(", "); - } - sb.append(table); - sb.append('.'); - sb.append(column); - } - return (sb == null) ? "" : sb.toString(); - } - - static { - // Contacts URI matching table - UriMatcher matcher = sURIMatcher; - matcher.addURI(CONTACTS_AUTHORITY, "extensions", EXTENSIONS); - matcher.addURI(CONTACTS_AUTHORITY, "extensions/#", EXTENSIONS_ID); - matcher.addURI(CONTACTS_AUTHORITY, "groups", GROUPS); - matcher.addURI(CONTACTS_AUTHORITY, "groups/#", GROUPS_ID); - matcher.addURI(CONTACTS_AUTHORITY, "groups/name/*/members", GROUP_NAME_MEMBERS); - matcher.addURI(CONTACTS_AUTHORITY, "groups/name/*/members/filter/*", - GROUP_NAME_MEMBERS_FILTER); - matcher.addURI(CONTACTS_AUTHORITY, "groups/system_id/*/members", GROUP_SYSTEM_ID_MEMBERS); - matcher.addURI(CONTACTS_AUTHORITY, "groups/system_id/*/members/filter/*", - GROUP_SYSTEM_ID_MEMBERS_FILTER); - matcher.addURI(CONTACTS_AUTHORITY, "groupmembership", GROUPMEMBERSHIP); - matcher.addURI(CONTACTS_AUTHORITY, "groupmembership/#", GROUPMEMBERSHIP_ID); - matcher.addURI(CONTACTS_AUTHORITY, "groupmembershipraw", GROUPMEMBERSHIP_RAW); - matcher.addURI(CONTACTS_AUTHORITY, "people", PEOPLE); - matcher.addURI(CONTACTS_AUTHORITY, "people/strequent", PEOPLE_STREQUENT); - matcher.addURI(CONTACTS_AUTHORITY, "people/strequent/filter/*", PEOPLE_STREQUENT_FILTER); - matcher.addURI(CONTACTS_AUTHORITY, "people/filter/*", PEOPLE_FILTER); - matcher.addURI(CONTACTS_AUTHORITY, "people/with_phones_filter/*", - PEOPLE_WITH_PHONES_FILTER); - matcher.addURI(CONTACTS_AUTHORITY, "people/#", PEOPLE_ID); - matcher.addURI(CONTACTS_AUTHORITY, "people/#/extensions", PEOPLE_EXTENSIONS); - matcher.addURI(CONTACTS_AUTHORITY, "people/#/extensions/#", PEOPLE_EXTENSIONS_ID); - matcher.addURI(CONTACTS_AUTHORITY, "people/#/phones", PEOPLE_PHONES); - matcher.addURI(CONTACTS_AUTHORITY, "people/#/photo", PEOPLE_PHOTO); - matcher.addURI(CONTACTS_AUTHORITY, "people/#/phones/#", PEOPLE_PHONES_ID); - matcher.addURI(CONTACTS_AUTHORITY, "people/#/contact_methods", PEOPLE_CONTACTMETHODS); - matcher.addURI(CONTACTS_AUTHORITY, "people/#/contact_methods_with_presence", - PEOPLE_CONTACTMETHODS_WITH_PRESENCE); - matcher.addURI(CONTACTS_AUTHORITY, "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID); - matcher.addURI(CONTACTS_AUTHORITY, "people/#/organizations", PEOPLE_ORGANIZATIONS); - matcher.addURI(CONTACTS_AUTHORITY, "people/#/organizations/#", PEOPLE_ORGANIZATIONS_ID); - matcher.addURI(CONTACTS_AUTHORITY, "people/#/groupmembership", PEOPLE_GROUPMEMBERSHIP); - matcher.addURI(CONTACTS_AUTHORITY, "people/#/groupmembership/#", PEOPLE_GROUPMEMBERSHIP_ID); - matcher.addURI(CONTACTS_AUTHORITY, "people/raw", PEOPLE_RAW); - matcher.addURI(CONTACTS_AUTHORITY, "people/owner", PEOPLE_OWNER); - matcher.addURI(CONTACTS_AUTHORITY, "people/#/update_contact_time", - PEOPLE_UPDATE_CONTACT_TIME); - matcher.addURI(CONTACTS_AUTHORITY, "deleted_people", DELETED_PEOPLE); - matcher.addURI(CONTACTS_AUTHORITY, "deleted_groups", DELETED_GROUPS); - matcher.addURI(CONTACTS_AUTHORITY, "phones", PHONES); - matcher.addURI(CONTACTS_AUTHORITY, "phones/filter/*", PHONES_FILTER); - matcher.addURI(CONTACTS_AUTHORITY, "phones/filter_name/*", PHONES_FILTER_NAME); - matcher.addURI(CONTACTS_AUTHORITY, "phones/mobile_filter_name/*", - PHONES_MOBILE_FILTER_NAME); - matcher.addURI(CONTACTS_AUTHORITY, "phones/#", PHONES_ID); - matcher.addURI(CONTACTS_AUTHORITY, "photos", PHOTOS); - matcher.addURI(CONTACTS_AUTHORITY, "photos/#", PHOTOS_ID); - matcher.addURI(CONTACTS_AUTHORITY, "contact_methods", CONTACTMETHODS); - matcher.addURI(CONTACTS_AUTHORITY, "contact_methods/email", CONTACTMETHODS_EMAIL); - matcher.addURI(CONTACTS_AUTHORITY, "contact_methods/email/*", CONTACTMETHODS_EMAIL_FILTER); - matcher.addURI(CONTACTS_AUTHORITY, "contact_methods/#", CONTACTMETHODS_ID); - matcher.addURI(CONTACTS_AUTHORITY, "contact_methods/with_presence", - CONTACTMETHODS_WITH_PRESENCE); - matcher.addURI(CONTACTS_AUTHORITY, "presence", PRESENCE); - matcher.addURI(CONTACTS_AUTHORITY, "presence/#", PRESENCE_ID); - matcher.addURI(CONTACTS_AUTHORITY, "organizations", ORGANIZATIONS); - matcher.addURI(CONTACTS_AUTHORITY, "organizations/#", ORGANIZATIONS_ID); - matcher.addURI(CONTACTS_AUTHORITY, "voice_dialer_timestamp", VOICE_DIALER_TIMESTAMP); - matcher.addURI(CONTACTS_AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, - SEARCH_SUGGESTIONS); - matcher.addURI(CONTACTS_AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", - SEARCH_SUGGESTIONS); - matcher.addURI(CONTACTS_AUTHORITY, "settings", SETTINGS); - - matcher.addURI(CONTACTS_AUTHORITY, "live_folders/people", LIVE_FOLDERS_PEOPLE); - matcher.addURI(CONTACTS_AUTHORITY, "live_folders/people/*", - LIVE_FOLDERS_PEOPLE_GROUP_NAME); - matcher.addURI(CONTACTS_AUTHORITY, "live_folders/people_with_phones", - LIVE_FOLDERS_PEOPLE_WITH_PHONES); - matcher.addURI(CONTACTS_AUTHORITY, "live_folders/favorites", - LIVE_FOLDERS_PEOPLE_FAVORITES); - - // Call log URI matching table - matcher.addURI(CALL_LOG_AUTHORITY, "calls", CALLS); - matcher.addURI(CALL_LOG_AUTHORITY, "calls/filter/*", CALLS_FILTER); - matcher.addURI(CALL_LOG_AUTHORITY, "calls/#", CALLS_ID); - - HashMap<String, String> map; - - // Create the common people columns - HashMap<String, String> peopleColumns = new HashMap<String, String>(); - peopleColumns.put(PeopleColumns.NAME, People.NAME); - peopleColumns.put(PeopleColumns.NOTES, People.NOTES); - peopleColumns.put(PeopleColumns.TIMES_CONTACTED, People.TIMES_CONTACTED); - peopleColumns.put(PeopleColumns.LAST_TIME_CONTACTED, People.LAST_TIME_CONTACTED); - peopleColumns.put(PeopleColumns.STARRED, People.STARRED); - peopleColumns.put(PeopleColumns.CUSTOM_RINGTONE, People.CUSTOM_RINGTONE); - peopleColumns.put(PeopleColumns.SEND_TO_VOICEMAIL, People.SEND_TO_VOICEMAIL); - peopleColumns.put(PeopleColumns.PHONETIC_NAME, People.PHONETIC_NAME); - peopleColumns.put(PeopleColumns.DISPLAY_NAME, - DISPLAY_NAME_SQL + " AS " + People.DISPLAY_NAME); - - // Create the common groups columns - HashMap<String, String> groupsColumns = new HashMap<String, String>(); - groupsColumns.put(GroupsColumns.NAME, Groups.NAME); - groupsColumns.put(GroupsColumns.NOTES, Groups.NOTES); - groupsColumns.put(GroupsColumns.SYSTEM_ID, Groups.SYSTEM_ID); - groupsColumns.put(GroupsColumns.SHOULD_SYNC, Groups.SHOULD_SYNC); - - // Create the common presence columns - HashMap<String, String> presenceColumns = new HashMap<String, String>(); - presenceColumns.put(PresenceColumns.IM_PROTOCOL, PresenceColumns.IM_PROTOCOL); - presenceColumns.put(PresenceColumns.IM_HANDLE, PresenceColumns.IM_HANDLE); - presenceColumns.put(PresenceColumns.IM_ACCOUNT, PresenceColumns.IM_ACCOUNT); - presenceColumns.put(PresenceColumns.PRESENCE_STATUS, PresenceColumns.PRESENCE_STATUS); - presenceColumns.put(PresenceColumns.PRESENCE_CUSTOM_STATUS, - PresenceColumns.PRESENCE_CUSTOM_STATUS); - - // Create the common sync columns - HashMap<String, String> syncColumns = new HashMap<String, String>(); - syncColumns.put(SyncConstValue._SYNC_ID, SyncConstValue._SYNC_ID); - syncColumns.put(SyncConstValue._SYNC_TIME, SyncConstValue._SYNC_TIME); - syncColumns.put(SyncConstValue._SYNC_VERSION, SyncConstValue._SYNC_VERSION); - syncColumns.put(SyncConstValue._SYNC_LOCAL_ID, SyncConstValue._SYNC_LOCAL_ID); - syncColumns.put(SyncConstValue._SYNC_DIRTY, SyncConstValue._SYNC_DIRTY); - syncColumns.put(SyncConstValue._SYNC_ACCOUNT, SyncConstValue._SYNC_ACCOUNT); - - // Phones columns - HashMap<String, String> phonesColumns = new HashMap<String, String>(); - phonesColumns.put(Phones.NUMBER, Phones.NUMBER); - phonesColumns.put(Phones.NUMBER_KEY, Phones.NUMBER_KEY); - phonesColumns.put(Phones.TYPE, Phones.TYPE); - phonesColumns.put(Phones.LABEL, Phones.LABEL); - - // People projection map - map = new HashMap<String, String>(); - map.put(People._ID, "people._id AS " + People._ID); - peopleColumns.put(People.PRIMARY_PHONE_ID, People.PRIMARY_PHONE_ID); - peopleColumns.put(People.PRIMARY_EMAIL_ID, People.PRIMARY_EMAIL_ID); - peopleColumns.put(People.PRIMARY_ORGANIZATION_ID, People.PRIMARY_ORGANIZATION_ID); - map.putAll(peopleColumns); - map.putAll(phonesColumns); - map.putAll(syncColumns); - map.putAll(presenceColumns); - sPeopleProjectionMap = map; - - // Groups projection map - map = new HashMap<String, String>(); - map.put(Groups._ID, Groups._ID); - map.putAll(groupsColumns); - map.putAll(syncColumns); - sGroupsProjectionMap = map; - - // Group Membership projection map - map = new HashMap<String, String>(); - map.put(GroupMembership._ID, "groupmembership._id AS " + GroupMembership._ID); - map.put(GroupMembership.PERSON_ID, GroupMembership.PERSON_ID); - map.put(GroupMembership.GROUP_ID, "groups._id AS " + GroupMembership.GROUP_ID); - map.put(GroupMembership.GROUP_SYNC_ACCOUNT, GroupMembership.GROUP_SYNC_ACCOUNT); - map.put(GroupMembership.GROUP_SYNC_ID, GroupMembership.GROUP_SYNC_ID); - map.putAll(groupsColumns); - sGroupMembershipProjectionMap = map; - - // Use this when you need to force items to the top of a times_contacted list - map = new HashMap<String, String>(sPeopleProjectionMap); - map.put(People.TIMES_CONTACTED, Long.MAX_VALUE + " AS " + People.TIMES_CONTACTED); - sPeopleWithMaxTimesContactedProjectionMap = map; - - // Calls projection map - map = new HashMap<String, String>(); - map.put(Calls._ID, Calls._ID); - map.put(Calls.NUMBER, Calls.NUMBER); - map.put(Calls.DATE, Calls.DATE); - map.put(Calls.DURATION, Calls.DURATION); - map.put(Calls.TYPE, Calls.TYPE); - map.put(Calls.NEW, Calls.NEW); - map.put(Calls.CACHED_NAME, Calls.CACHED_NAME); - map.put(Calls.CACHED_NUMBER_TYPE, Calls.CACHED_NUMBER_TYPE); - map.put(Calls.CACHED_NUMBER_LABEL, Calls.CACHED_NUMBER_LABEL); - sCallsProjectionMap = map; - - // Phones projection map - map = new HashMap<String, String>(); - map.put(Phones._ID, "phones._id AS " + Phones._ID); - map.putAll(phonesColumns); - map.put(Phones.PERSON_ID, Phones.PERSON_ID); - map.put(Phones.ISPRIMARY, Phones.ISPRIMARY); - map.putAll(peopleColumns); - sPhonesProjectionMap = map; - - // Organizations projection map - map = new HashMap<String, String>(); - map.put(Organizations._ID, "organizations._id AS " + Organizations._ID); - map.put(Organizations.LABEL, Organizations.LABEL); - map.put(Organizations.TYPE, Organizations.TYPE); - map.put(Organizations.PERSON_ID, Organizations.PERSON_ID); - map.put(Organizations.COMPANY, Organizations.COMPANY); - map.put(Organizations.TITLE, Organizations.TITLE); - map.put(Organizations.ISPRIMARY, Organizations.ISPRIMARY); - sOrganizationsProjectionMap = map; - - // Extensions projection map - map = new HashMap<String, String>(); - map.put(Extensions._ID, Extensions._ID); - map.put(Extensions.NAME, Extensions.NAME); - map.put(Extensions.VALUE, Extensions.VALUE); - map.put(Extensions.PERSON_ID, Extensions.PERSON_ID); - sExtensionsProjectionMap = map; - - // Contact methods projection map - map = new HashMap<String, String>(); - map.put(ContactMethods._ID, "contact_methods._id AS " + ContactMethods._ID); - map.put(ContactMethods.KIND, ContactMethods.KIND); - map.put(ContactMethods.TYPE, ContactMethods.TYPE); - map.put(ContactMethods.LABEL, ContactMethods.LABEL); - map.put(ContactMethods.DATA, ContactMethods.DATA); - map.put(ContactMethods.AUX_DATA, ContactMethods.AUX_DATA); - map.put(ContactMethods.PERSON_ID, ContactMethods.PERSON_ID); - map.put(ContactMethods.ISPRIMARY, ContactMethods.ISPRIMARY); - map.putAll(peopleColumns); - sContactMethodsProjectionMap = map; - - // Contact methods with presence projection map - map = new HashMap<String, String>(sContactMethodsProjectionMap); - map.putAll(presenceColumns); - sContactMethodsWithPresenceProjectionMap = map; - - // Email search projection map - map = new HashMap<String, String>(); - map.put(ContactMethods.NAME, ContactMethods.NAME); - map.put(ContactMethods.DATA, ContactMethods.DATA); - map.put(ContactMethods._ID, "contact_methods._id AS " + ContactMethods._ID); - sEmailSearchProjectionMap = map; - - // Presence projection map - map = new HashMap<String, String>(); - map.put(Presence._ID, "presence._id AS " + Presence._ID); - map.putAll(presenceColumns); - map.putAll(peopleColumns); - sPresenceProjectionMap = map; - - // Search suggestions projection map - map = new HashMap<String, String>(); - map.put(SearchManager.SUGGEST_COLUMN_TEXT_1, - DISPLAY_NAME_SQL + " AS " + SearchManager.SUGGEST_COLUMN_TEXT_1); - map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, - People._ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); - map.put(People._ID, People._ID); - sSearchSuggestionsProjectionMap = map; - - // Photos projection map - map = new HashMap<String, String>(); - map.put(Photos._ID, Photos._ID); - map.put(Photos.LOCAL_VERSION, Photos.LOCAL_VERSION); - map.put(Photos.EXISTS_ON_SERVER, Photos.EXISTS_ON_SERVER); - map.put(Photos.SYNC_ERROR, Photos.SYNC_ERROR); - map.put(Photos.PERSON_ID, Photos.PERSON_ID); - map.put(Photos.DATA, Photos.DATA); - map.put(Photos.DOWNLOAD_REQUIRED, "" - + "(exists_on_server!=0 " - + " AND sync_error IS NULL " - + " AND (local_version IS NULL OR _sync_version != local_version)) " - + "AS " + Photos.DOWNLOAD_REQUIRED); - map.putAll(syncColumns); - sPhotosProjectionMap = map; - - // Live folder projection - map = new HashMap<String, String>(); - map.put(LiveFolders._ID, "people._id AS " + LiveFolders._ID); - map.put(LiveFolders.NAME, DISPLAY_NAME_SQL + " AS " + LiveFolders.NAME); - map.put(LiveFolders.ICON_BITMAP, Photos.DATA + " AS " + LiveFolders.ICON_BITMAP); - sLiveFoldersProjectionMap = map; - - // Order by statements - sPhonesKeyOrderBy = buildOrderBy(sPhonesTable, Phones.NUMBER); - sContactMethodsKeyOrderBy = buildOrderBy(sContactMethodsTable, - ContactMethods.DATA, ContactMethods.KIND); - sOrganizationsKeyOrderBy = buildOrderBy(sOrganizationsTable, Organizations.COMPANY); - sGroupmembershipKeyOrderBy = - buildOrderBy(sGroupmembershipTable, GroupMembership.GROUP_SYNC_ACCOUNT); - - sPhonesKeyColumns = new String[]{Phones.NUMBER}; - sContactMethodsKeyColumns = new String[]{ContactMethods.DATA, ContactMethods.KIND}; - sOrganizationsKeyColumns = new String[]{Organizations.COMPANY}; - sGroupmembershipKeyColumns = new String[]{GroupMembership.GROUP_SYNC_ACCOUNT}; - sExtensionsKeyColumns = new String[]{Extensions.NAME}; - - String groupJoinByLocalId = "groups._id=groupmembership.group_id"; - String groupJoinByServerId = "(" - + "groups._sync_account=groupmembership.group_sync_account" - + " AND " - + "groups._sync_id=groupmembership.group_sync_id" - + ")"; - sGroupsJoinString = "(" + groupJoinByLocalId + " OR " + groupJoinByServerId + ")"; - } -} |