aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk13
-rw-r--r--AndroidManifest.xml23
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--NOTICE190
-rw-r--r--res/drawable/app_icon.pngbin2995 -> 0 bytes
-rw-r--r--res/values/strings.xml26
-rw-r--r--src/com/android/providers/contacts/ContactsProvider.java4083
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
deleted file mode 100644
index 826656ff..00000000
--- a/res/drawable/app_icon.png
+++ /dev/null
Binary files differ
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 + ")";
- }
-}