/*
* Copyright (C) 2009 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 com.android.internal.content.SyncStateContentProviderHelper;
import com.android.providers.contacts.ContactLookupKey.LookupKeySegment;
import com.android.providers.contacts.ContactsDatabaseHelper.AggregatedPresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Clauses;
import com.android.providers.contacts.ContactsDatabaseHelper.ContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.ContactsStatusUpdatesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.DisplayNameSources;
import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType;
import com.android.providers.contacts.ContactsDatabaseHelper.NicknameLookupColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.PhoneColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.SettingsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.google.android.collect.Sets;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.OnAccountsUpdateListener;
import android.app.SearchManager;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Entity;
import android.content.EntityIterator;
import android.content.IContentService;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.content.SyncAdapterType;
import android.content.UriMatcher;
import android.content.SharedPreferences.Editor;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteContentHelper;
import android.database.sqlite.SQLiteCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.os.Bundle;
import android.os.MemoryFile;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.pim.vcard.VCardComposer;
import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.LiveFolders;
import android.provider.OpenableColumns;
import android.provider.SyncStateContract;
import android.provider.ContactsContract.AggregationExceptions;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.Settings;
import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
/**
* Contacts content provider. The contract between this provider and applications
* is defined in {@link ContactsContract}.
*/
public class ContactsProvider2 extends SQLiteContentProvider implements OnAccountsUpdateListener {
private static final String TAG = "ContactsProvider";
private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
// TODO: carefully prevent all incoming nested queries; they can be gaping security holes
// TODO: check for restricted flag during insert(), update(), and delete() calls
/** Default for the maximum number of returned aggregation suggestions. */
private static final int DEFAULT_MAX_SUGGESTIONS = 5;
/**
* Shared preference key for the legacy contact import version. The need for a version
* as opposed to a boolean flag is that if we discover bugs in the contact import process,
* we can trigger re-import by incrementing the import version.
*/
private static final String PREF_CONTACTS_IMPORTED = "contacts_imported_v1";
private static final int PREF_CONTACTS_IMPORT_VERSION = 1;
private static final String AGGREGATE_CONTACTS = "sync.contacts.aggregate";
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final String TIMES_CONTACED_SORT_COLUMN = "times_contacted_sort";
private static final String STREQUENT_ORDER_BY = Contacts.STARRED + " DESC, "
+ TIMES_CONTACED_SORT_COLUMN + " DESC, "
+ Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
private static final String STREQUENT_LIMIT =
"(SELECT COUNT(1) FROM " + Tables.CONTACTS + " WHERE "
+ Contacts.STARRED + "=1) + 25";
private static final int CONTACTS = 1000;
private static final int CONTACTS_ID = 1001;
private static final int CONTACTS_LOOKUP = 1002;
private static final int CONTACTS_LOOKUP_ID = 1003;
private static final int CONTACTS_DATA = 1004;
private static final int CONTACTS_FILTER = 1005;
private static final int CONTACTS_STREQUENT = 1006;
private static final int CONTACTS_STREQUENT_FILTER = 1007;
private static final int CONTACTS_GROUP = 1008;
private static final int CONTACTS_PHOTO = 1009;
private static final int CONTACTS_AS_VCARD = 1010;
private static final int RAW_CONTACTS = 2002;
private static final int RAW_CONTACTS_ID = 2003;
private static final int RAW_CONTACTS_DATA = 2004;
private static final int RAW_CONTACT_ENTITY_ID = 2005;
private static final int DATA = 3000;
private static final int DATA_ID = 3001;
private static final int PHONES = 3002;
private static final int PHONES_ID = 3003;
private static final int PHONES_FILTER = 3004;
private static final int EMAILS = 3005;
private static final int EMAILS_ID = 3006;
private static final int EMAILS_LOOKUP = 3007;
private static final int EMAILS_FILTER = 3008;
private static final int POSTALS = 3009;
private static final int POSTALS_ID = 3010;
private static final int PHONE_LOOKUP = 4000;
private static final int AGGREGATION_EXCEPTIONS = 6000;
private static final int AGGREGATION_EXCEPTION_ID = 6001;
private static final int STATUS_UPDATES = 7000;
private static final int STATUS_UPDATES_ID = 7001;
private static final int AGGREGATION_SUGGESTIONS = 8000;
private static final int SETTINGS = 9000;
private static final int GROUPS = 10000;
private static final int GROUPS_ID = 10001;
private static final int GROUPS_SUMMARY = 10003;
private static final int SYNCSTATE = 11000;
private static final int SYNCSTATE_ID = 11001;
private static final int SEARCH_SUGGESTIONS = 12001;
private static final int SEARCH_SHORTCUT = 12002;
private static final int LIVE_FOLDERS_CONTACTS = 14000;
private static final int LIVE_FOLDERS_CONTACTS_WITH_PHONES = 14001;
private static final int LIVE_FOLDERS_CONTACTS_FAVORITES = 14002;
private static final int LIVE_FOLDERS_CONTACTS_GROUP_NAME = 14003;
private static final int RAW_CONTACT_ENTITIES = 15001;
private interface ContactsQuery {
public static final String TABLE = Tables.RAW_CONTACTS;
public static final String[] PROJECTION = new String[] {
RawContactsColumns.CONCRETE_ID,
RawContacts.ACCOUNT_NAME,
RawContacts.ACCOUNT_TYPE,
};
public static final int RAW_CONTACT_ID = 0;
public static final int ACCOUNT_NAME = 1;
public static final int ACCOUNT_TYPE = 2;
}
private interface DataContactsQuery {
public static final String TABLE = "data "
+ "JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) "
+ "JOIN contacts ON (raw_contacts.contact_id = contacts._id)";
public static final String[] PROJECTION = new String[] {
RawContactsColumns.CONCRETE_ID,
DataColumns.CONCRETE_ID,
ContactsColumns.CONCRETE_ID
};
public static final int RAW_CONTACT_ID = 0;
public static final int DATA_ID = 1;
public static final int CONTACT_ID = 2;
}
private interface DisplayNameQuery {
public static final String TABLE = Tables.DATA_JOIN_MIMETYPES;
public static final String[] COLUMNS = new String[] {
MimetypesColumns.MIMETYPE,
Data.IS_PRIMARY,
Data.DATA1,
Organization.TITLE,
};
public static final int MIMETYPE = 0;
public static final int IS_PRIMARY = 1;
public static final int DATA = 2;
public static final int TITLE = 3;
}
private interface DataDeleteQuery {
public static final String TABLE = Tables.DATA_JOIN_MIMETYPES;
public static final String[] CONCRETE_COLUMNS = new String[] {
DataColumns.CONCRETE_ID,
MimetypesColumns.MIMETYPE,
Data.RAW_CONTACT_ID,
Data.IS_PRIMARY,
Data.DATA1,
};
public static final String[] COLUMNS = new String[] {
Data._ID,
MimetypesColumns.MIMETYPE,
Data.RAW_CONTACT_ID,
Data.IS_PRIMARY,
Data.DATA1,
};
public static final int _ID = 0;
public static final int MIMETYPE = 1;
public static final int RAW_CONTACT_ID = 2;
public static final int IS_PRIMARY = 3;
public static final int DATA1 = 4;
}
private interface DataUpdateQuery {
String[] COLUMNS = { Data._ID, Data.RAW_CONTACT_ID, Data.MIMETYPE };
int _ID = 0;
int RAW_CONTACT_ID = 1;
int MIMETYPE = 2;
}
private interface NicknameLookupQuery {
String TABLE = Tables.NICKNAME_LOOKUP;
String[] COLUMNS = new String[] {
NicknameLookupColumns.CLUSTER
};
int CLUSTER = 0;
}
private interface RawContactsQuery {
String TABLE = Tables.RAW_CONTACTS;
String[] COLUMNS = new String[] {
ContactsContract.RawContacts.DELETED
};
int DELETED = 0;
}
private static final HashMap<String, Integer> sDisplayNameSources;
static {
sDisplayNameSources = new HashMap<String, Integer>();
sDisplayNameSources.put(StructuredName.CONTENT_ITEM_TYPE,
DisplayNameSources.STRUCTURED_NAME);
sDisplayNameSources.put(Nickname.CONTENT_ITEM_TYPE,
DisplayNameSources.NICKNAME);
sDisplayNameSources.put(Organization.CONTENT_ITEM_TYPE,
DisplayNameSources.ORGANIZATION);
sDisplayNameSources.put(Phone.CONTENT_ITEM_TYPE,
DisplayNameSources.PHONE);
sDisplayNameSources.put(Email.CONTENT_ITEM_TYPE,
DisplayNameSources.EMAIL);
}
public static final String DEFAULT_ACCOUNT_TYPE = "com.google";
public static final String FEATURE_LEGACY_HOSTED_OR_GOOGLE = "legacy_hosted_or_google";
/** Sql where statement for filtering on groups. */
private static final String CONTACTS_IN_GROUP_SELECT =
Contacts._ID + " IN "
+ "(SELECT " + RawContacts.CONTACT_ID
+ " FROM " + Tables.RAW_CONTACTS
+ " WHERE " + RawContactsColumns.CONCRETE_ID + " IN "
+ "(SELECT " + DataColumns.CONCRETE_RAW_CONTACT_ID
+ " FROM " + Tables.DATA_JOIN_MIMETYPES
+ " WHERE " + Data.MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE
+ "' AND " + GroupMembership.GROUP_ROW_ID + "="
+ "(SELECT " + Tables.GROUPS + "." + Groups._ID
+ " FROM " + Tables.GROUPS
+ " WHERE " + Groups.TITLE + "=?)))";
/** Contains just BaseColumns._COUNT */
private static final HashMap<String, String> sCountProjectionMap;
/** Contains just the contacts columns */
private static final HashMap<String, String> sContactsProjectionMap;
/** Used for pushing starred contacts to the top of a times contacted list **/
private static final HashMap<String, String> sStrequentStarredProjectionMap;
private static final HashMap<String, String> sStrequentFrequentProjectionMap;
/** Contains just the contacts vCard columns */
private static final HashMap<String, String> sContactsVCardProjectionMap;
/** Contains just the raw contacts columns */
private static final HashMap<String, String> sRawContactsProjectionMap;
/** Contains the columns from the raw contacts entity view*/
private static final HashMap<String, String> sRawContactsEntityProjectionMap;
/** Contains columns from the data view */
private static final HashMap<String, String> sDataProjectionMap;
/** Contains columns from the data view */
private static final HashMap<String, String> sDistinctDataProjectionMap;
/** Contains the data and contacts columns, for joined tables */
private static final HashMap<String, String> sPhoneLookupProjectionMap;
/** Contains the just the {@link Groups} columns */
private static final HashMap<String, String> sGroupsProjectionMap;
/** Contains {@link Groups} columns along with summary details */
private static final HashMap<String, String> sGroupsSummaryProjectionMap;
/** Contains the agg_exceptions columns */
private static final HashMap<String, String> sAggregationExceptionsProjectionMap;
/** Contains the agg_exceptions columns */
private static final HashMap<String, String> sSettingsProjectionMap;
/** Contains StatusUpdates columns */
private static final HashMap<String, String> sStatusUpdatesProjectionMap;
/** Contains Live Folders columns */
private static final HashMap<String, String> sLiveFoldersProjectionMap;
/** Precompiled sql statement for setting a data record to the primary. */
private SQLiteStatement mSetPrimaryStatement;
/** Precompiled sql statement for setting a data record to the super primary. */
private SQLiteStatement mSetSuperPrimaryStatement;
/** Precompiled sql statement for incrementing times contacted for a contact */
private SQLiteStatement mContactsLastTimeContactedUpdate;
/** Precompiled sql statement for updating a contact display name */
private SQLiteStatement mRawContactDisplayNameUpdate;
/** Precompiled sql statement for marking a raw contact as dirty */
private SQLiteStatement mRawContactDirtyUpdate;
/** Precompiled sql statement for updating an aggregated status update */
private SQLiteStatement mLastStatusUpdate;
private SQLiteStatement mNameLookupInsert;
private SQLiteStatement mNameLookupDelete;
private SQLiteStatement mStatusUpdateAutoTimestamp;
private SQLiteStatement mStatusUpdateInsert;
private SQLiteStatement mStatusUpdateReplace;
private SQLiteStatement mStatusAttributionUpdate;
private SQLiteStatement mStatusUpdateDelete;
private long mMimeTypeIdEmail;
private long mMimeTypeIdIm;
private StringBuilder mSb = new StringBuilder();
static {
// Contacts URI matching table
final UriMatcher matcher = sUriMatcher;
matcher.addURI(ContactsContract.AUTHORITY, "contacts", CONTACTS);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#", CONTACTS_ID);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/data", CONTACTS_DATA);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/suggestions",
AGGREGATION_SUGGESTIONS);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/suggestions/*",
AGGREGATION_SUGGESTIONS);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/photo", CONTACTS_PHOTO);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/filter/*", CONTACTS_FILTER);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*", CONTACTS_LOOKUP);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#", CONTACTS_LOOKUP_ID);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/as_vcard/*", CONTACTS_AS_VCARD);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/strequent/", CONTACTS_STREQUENT);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/strequent/filter/*",
CONTACTS_STREQUENT_FILTER);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/group/*", CONTACTS_GROUP);
matcher.addURI(ContactsContract.AUTHORITY, "raw_contacts", RAW_CONTACTS);
matcher.addURI(ContactsContract.AUTHORITY, "raw_contacts/#", RAW_CONTACTS_ID);
matcher.addURI(ContactsContract.AUTHORITY, "raw_contacts/#/data", RAW_CONTACTS_DATA);
matcher.addURI(ContactsContract.AUTHORITY, "raw_contacts/#/entity", RAW_CONTACT_ENTITY_ID);
matcher.addURI(ContactsContract.AUTHORITY, "raw_contact_entities", RAW_CONTACT_ENTITIES);
matcher.addURI(ContactsContract.AUTHORITY, "data", DATA);
matcher.addURI(ContactsContract.AUTHORITY, "data/#", DATA_ID);
matcher.addURI(ContactsContract.AUTHORITY, "data/phones", PHONES);
matcher.addURI(ContactsContract.AUTHORITY, "data/phones/#", PHONES_ID);
matcher.addURI(ContactsContract.AUTHORITY, "data/phones/filter", PHONES_FILTER);
matcher.addURI(ContactsContract.AUTHORITY, "data/phones/filter/*", PHONES_FILTER);
matcher.addURI(ContactsContract.AUTHORITY, "data/emails", EMAILS);
matcher.addURI(ContactsContract.AUTHORITY, "data/emails/#", EMAILS_ID);
matcher.addURI(ContactsContract.AUTHORITY, "data/emails/lookup/*", EMAILS_LOOKUP);
matcher.addURI(ContactsContract.AUTHORITY, "data/emails/filter", EMAILS_FILTER);
matcher.addURI(ContactsContract.AUTHORITY, "data/emails/filter/*", EMAILS_FILTER);
matcher.addURI(ContactsContract.AUTHORITY, "data/postals", POSTALS);
matcher.addURI(ContactsContract.AUTHORITY, "data/postals/#", POSTALS_ID);
matcher.addURI(ContactsContract.AUTHORITY, "groups", GROUPS);
matcher.addURI(ContactsContract.AUTHORITY, "groups/#", GROUPS_ID);
matcher.addURI(ContactsContract.AUTHORITY, "groups_summary", GROUPS_SUMMARY);
matcher.addURI(ContactsContract.AUTHORITY, SyncStateContentProviderHelper.PATH, SYNCSTATE);
matcher.addURI(ContactsContract.AUTHORITY, SyncStateContentProviderHelper.PATH + "/#",
SYNCSTATE_ID);
matcher.addURI(ContactsContract.AUTHORITY, "phone_lookup/*", PHONE_LOOKUP);
matcher.addURI(ContactsContract.AUTHORITY, "aggregation_exceptions",
AGGREGATION_EXCEPTIONS);
matcher.addURI(ContactsContract.AUTHORITY, "aggregation_exceptions/*",
AGGREGATION_EXCEPTION_ID);
matcher.addURI(ContactsContract.AUTHORITY, "settings", SETTINGS);
matcher.addURI(ContactsContract.AUTHORITY, "status_updates", STATUS_UPDATES);
matcher.addURI(ContactsContract.AUTHORITY, "status_updates/#", STATUS_UPDATES_ID);
matcher.addURI(ContactsContract.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY,
SEARCH_SUGGESTIONS);
matcher.addURI(ContactsContract.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
SEARCH_SUGGESTIONS);
matcher.addURI(ContactsContract.AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/#",
SEARCH_SHORTCUT);
matcher.addURI(ContactsContract.AUTHORITY, "live_folders/contacts",
LIVE_FOLDERS_CONTACTS);
matcher.addURI(ContactsContract.AUTHORITY, "live_folders/contacts/*",
LIVE_FOLDERS_CONTACTS_GROUP_NAME);
matcher.addURI(ContactsContract.AUTHORITY, "live_folders/contacts_with_phones",
LIVE_FOLDERS_CONTACTS_WITH_PHONES);
matcher.addURI(ContactsContract.AUTHORITY, "live_folders/favorites",
LIVE_FOLDERS_CONTACTS_FAVORITES);
}
static {
sCountProjectionMap = new HashMap<String, String>();
sCountProjectionMap.put(BaseColumns._COUNT, "COUNT(*)");
sContactsProjectionMap = new HashMap<String, String>();
sContactsProjectionMap.put(Contacts._ID, Contacts._ID);
sContactsProjectionMap.put(Contacts.DISPLAY_NAME, Contacts.DISPLAY_NAME);
sContactsProjectionMap.put(Contacts.LAST_TIME_CONTACTED, Contacts.LAST_TIME_CONTACTED);
sContactsProjectionMap.put(Contacts.TIMES_CONTACTED, Contacts.TIMES_CONTACTED);
sContactsProjectionMap.put(Contacts.STARRED, Contacts.STARRED);
sContactsProjectionMap.put(Contacts.IN_VISIBLE_GROUP, Contacts.IN_VISIBLE_GROUP);
sContactsProjectionMap.put(Contacts.PHOTO_ID, Contacts.PHOTO_ID);
sContactsProjectionMap.put(Contacts.CUSTOM_RINGTONE, Contacts.CUSTOM_RINGTONE);
sContactsProjectionMap.put(Contacts.HAS_PHONE_NUMBER, Contacts.HAS_PHONE_NUMBER);
sContactsProjectionMap.put(Contacts.SEND_TO_VOICEMAIL, Contacts.SEND_TO_VOICEMAIL);
sContactsProjectionMap.put(Contacts.LOOKUP_KEY, Contacts.LOOKUP_KEY);
// Handle projections for Contacts-level statuses
addProjection(sContactsProjectionMap, Contacts.CONTACT_PRESENCE,
Tables.AGGREGATED_PRESENCE + "." + StatusUpdates.PRESENCE);
addProjection(sContactsProjectionMap, Contacts.CONTACT_STATUS,
ContactsStatusUpdatesColumns.CONCRETE_STATUS);
addProjection(sContactsProjectionMap, Contacts.CONTACT_STATUS_TIMESTAMP,
ContactsStatusUpdatesColumns.CONCRETE_STATUS_TIMESTAMP);
addProjection(sContactsProjectionMap, Contacts.CONTACT_STATUS_RES_PACKAGE,
ContactsStatusUpdatesColumns.CONCRETE_STATUS_RES_PACKAGE);
addProjection(sContactsProjectionMap, Contacts.CONTACT_STATUS_LABEL,
ContactsStatusUpdatesColumns.CONCRETE_STATUS_LABEL);
addProjection(sContactsProjectionMap, Contacts.CONTACT_STATUS_ICON,
ContactsStatusUpdatesColumns.CONCRETE_STATUS_ICON);
sStrequentStarredProjectionMap = new HashMap<String, String>(sContactsProjectionMap);
sStrequentStarredProjectionMap.put(TIMES_CONTACED_SORT_COLUMN,
Long.MAX_VALUE + " AS " + TIMES_CONTACED_SORT_COLUMN);
sStrequentFrequentProjectionMap = new HashMap<String, String>(sContactsProjectionMap);
sStrequentFrequentProjectionMap.put(TIMES_CONTACED_SORT_COLUMN,
Contacts.TIMES_CONTACTED + " AS " + TIMES_CONTACED_SORT_COLUMN);
sContactsVCardProjectionMap = Maps.newHashMap();
sContactsVCardProjectionMap.put(OpenableColumns.DISPLAY_NAME, Contacts.DISPLAY_NAME
+ " || '.vcf' AS " + OpenableColumns.DISPLAY_NAME);
sContactsVCardProjectionMap.put(OpenableColumns.SIZE, "0 AS " + OpenableColumns.SIZE);
sRawContactsProjectionMap = new HashMap<String, String>();
sRawContactsProjectionMap.put(RawContacts._ID, RawContacts._ID);
sRawContactsProjectionMap.put(RawContacts.CONTACT_ID, RawContacts.CONTACT_ID);
sRawContactsProjectionMap.put(RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_NAME);
sRawContactsProjectionMap.put(RawContacts.ACCOUNT_TYPE, RawContacts.ACCOUNT_TYPE);
sRawContactsProjectionMap.put(RawContacts.SOURCE_ID, RawContacts.SOURCE_ID);
sRawContactsProjectionMap.put(RawContacts.VERSION, RawContacts.VERSION);
sRawContactsProjectionMap.put(RawContacts.DIRTY, RawContacts.DIRTY);
sRawContactsProjectionMap.put(RawContacts.DELETED, RawContacts.DELETED);
sRawContactsProjectionMap.put(RawContacts.TIMES_CONTACTED, RawContacts.TIMES_CONTACTED);
sRawContactsProjectionMap.put(RawContacts.LAST_TIME_CONTACTED,
RawContacts.LAST_TIME_CONTACTED);
sRawContactsProjectionMap.put(RawContacts.CUSTOM_RINGTONE, RawContacts.CUSTOM_RINGTONE);
sRawContactsProjectionMap.put(RawContacts.SEND_TO_VOICEMAIL, RawContacts.SEND_TO_VOICEMAIL);
sRawContactsProjectionMap.put(RawContacts.STARRED, RawContacts.STARRED);
sRawContactsProjectionMap.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE);
sRawContactsProjectionMap.put(RawContacts.SYNC1, RawContacts.SYNC1);
sRawContactsProjectionMap.put(RawContacts.SYNC2, RawContacts.SYNC2);
sRawContactsProjectionMap.put(RawContacts.SYNC3, RawContacts.SYNC3);
sRawContactsProjectionMap.put(RawContacts.SYNC4, RawContacts.SYNC4);
sDataProjectionMap = new HashMap<String, String>();
sDataProjectionMap.put(Data._ID, Data._ID);
sDataProjectionMap.put(Data.RAW_CONTACT_ID, Data.RAW_CONTACT_ID);
sDataProjectionMap.put(Data.DATA_VERSION, Data.DATA_VERSION);
sDataProjectionMap.put(Data.IS_PRIMARY, Data.IS_PRIMARY);
sDataProjectionMap.put(Data.IS_SUPER_PRIMARY, Data.IS_SUPER_PRIMARY);
sDataProjectionMap.put(Data.RES_PACKAGE, Data.RES_PACKAGE);
sDataProjectionMap.put(Data.MIMETYPE, Data.MIMETYPE);
sDataProjectionMap.put(Data.DATA1, Data.DATA1);
sDataProjectionMap.put(Data.DATA2, Data.DATA2);
sDataProjectionMap.put(Data.DATA3, Data.DATA3);
sDataProjectionMap.put(Data.DATA4, Data.DATA4);
sDataProjectionMap.put(Data.DATA5, Data.DATA5);
sDataProjectionMap.put(Data.DATA6, Data.DATA6);
sDataProjectionMap.put(Data.DATA7, Data.DATA7);
sDataProjectionMap.put(Data.DATA8, Data.DATA8);
sDataProjectionMap.put(Data.DATA9, Data.DATA9);
sDataProjectionMap.put(Data.DATA10, Data.DATA10);
sDataProjectionMap.put(Data.DATA11, Data.DATA11);
sDataProjectionMap.put(Data.DATA12, Data.DATA12);
sDataProjectionMap.put(Data.DATA13, Data.DATA13);
sDataProjectionMap.put(Data.DATA14, Data.DATA14);
sDataProjectionMap.put(Data.DATA15, Data.DATA15);
sDataProjectionMap.put(Data.SYNC1, Data.SYNC1);
sDataProjectionMap.put(Data.SYNC2, Data.SYNC2);
sDataProjectionMap.put(Data.SYNC3, Data.SYNC3);
sDataProjectionMap.put(Data.SYNC4, Data.SYNC4);
sDataProjectionMap.put(Data.CONTACT_ID, Data.CONTACT_ID);
sDataProjectionMap.put(RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_NAME);
sDataProjectionMap.put(RawContacts.ACCOUNT_TYPE, RawContacts.ACCOUNT_TYPE);
sDataProjectionMap.put(RawContacts.SOURCE_ID, RawContacts.SOURCE_ID);
sDataProjectionMap.put(RawContacts.VERSION, RawContacts.VERSION);
sDataProjectionMap.put(RawContacts.DIRTY, RawContacts.DIRTY);
sDataProjectionMap.put(Contacts.LOOKUP_KEY, Contacts.LOOKUP_KEY);
sDataProjectionMap.put(Contacts.DISPLAY_NAME, Contacts.DISPLAY_NAME);
sDataProjectionMap.put(Contacts.CUSTOM_RINGTONE, Contacts.CUSTOM_RINGTONE);
sDataProjectionMap.put(Contacts.SEND_TO_VOICEMAIL, Contacts.SEND_TO_VOICEMAIL);
sDataProjectionMap.put(Contacts.LAST_TIME_CONTACTED, Contacts.LAST_TIME_CONTACTED);
sDataProjectionMap.put(Contacts.TIMES_CONTACTED, Contacts.TIMES_CONTACTED);
sDataProjectionMap.put(Contacts.STARRED, Contacts.STARRED);
sDataProjectionMap.put(Contacts.PHOTO_ID, Contacts.PHOTO_ID);
sDataProjectionMap.put(Contacts.IN_VISIBLE_GROUP, Contacts.IN_VISIBLE_GROUP);
sDataProjectionMap.put(GroupMembership.GROUP_SOURCE_ID, GroupMembership.GROUP_SOURCE_ID);
HashMap<String, String> columns;
columns = new HashMap<String, String>();
columns.put(RawContacts._ID, RawContacts._ID);
columns.put(RawContacts.CONTACT_ID, RawContacts.CONTACT_ID);
columns.put(RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_NAME);
columns.put(RawContacts.ACCOUNT_TYPE, RawContacts.ACCOUNT_TYPE);
columns.put(RawContacts.SOURCE_ID, RawContacts.SOURCE_ID);
columns.put(RawContacts.VERSION, RawContacts.VERSION);
columns.put(RawContacts.DIRTY, RawContacts.DIRTY);
columns.put(RawContacts.DELETED, RawContacts.DELETED);
columns.put(RawContacts.IS_RESTRICTED, RawContacts.IS_RESTRICTED);
columns.put(RawContacts.SYNC1, RawContacts.SYNC1);
columns.put(RawContacts.SYNC2, RawContacts.SYNC2);
columns.put(RawContacts.SYNC3, RawContacts.SYNC3);
columns.put(RawContacts.SYNC4, RawContacts.SYNC4);
columns.put(Data.RES_PACKAGE, Data.RES_PACKAGE);
columns.put(Data.MIMETYPE, Data.MIMETYPE);
columns.put(Data.DATA1, Data.DATA1);
columns.put(Data.DATA2, Data.DATA2);
columns.put(Data.DATA3, Data.DATA3);
columns.put(Data.DATA4, Data.DATA4);
columns.put(Data.DATA5, Data.DATA5);
columns.put(Data.DATA6, Data.DATA6);
columns.put(Data.DATA7, Data.DATA7);
columns.put(Data.DATA8, Data.DATA8);
columns.put(Data.DATA9, Data.DATA9);
columns.put(Data.DATA10, Data.DATA10);
columns.put(Data.DATA11, Data.DATA11);
columns.put(Data.DATA12, Data.DATA12);
columns.put(Data.DATA13, Data.DATA13);
columns.put(Data.DATA14, Data.DATA14);
columns.put(Data.DATA15, Data.DATA15);
columns.put(Data.SYNC1, Data.SYNC1);
columns.put(Data.SYNC2, Data.SYNC2);
columns.put(Data.SYNC3, Data.SYNC3);
columns.put(Data.SYNC4, Data.SYNC4);
columns.put(RawContacts.Entity.DATA_ID, RawContacts.Entity.DATA_ID);
columns.put(Data.STARRED, Data.STARRED);
columns.put(Data.DATA_VERSION, Data.DATA_VERSION);
columns.put(Data.IS_PRIMARY, Data.IS_PRIMARY);
columns.put(Data.IS_SUPER_PRIMARY, Data.IS_SUPER_PRIMARY);
columns.put(GroupMembership.GROUP_SOURCE_ID, GroupMembership.GROUP_SOURCE_ID);
sRawContactsEntityProjectionMap = columns;
// Handle projections for Contacts-level statuses
addProjection(sDataProjectionMap, Contacts.CONTACT_PRESENCE,
Tables.AGGREGATED_PRESENCE + "." + StatusUpdates.PRESENCE);
addProjection(sDataProjectionMap, Contacts.CONTACT_STATUS,
ContactsStatusUpdatesColumns.CONCRETE_STATUS);
addProjection(sDataProjectionMap, Contacts.CONTACT_STATUS_TIMESTAMP,
ContactsStatusUpdatesColumns.CONCRETE_STATUS_TIMESTAMP);
addProjection(sDataProjectionMap, Contacts.CONTACT_STATUS_RES_PACKAGE,
ContactsStatusUpdatesColumns.CONCRETE_STATUS_RES_PACKAGE);
addProjection(sDataProjectionMap, Contacts.CONTACT_STATUS_LABEL,
ContactsStatusUpdatesColumns.CONCRETE_STATUS_LABEL);
addProjection(sDataProjectionMap, Contacts.CONTACT_STATUS_ICON,
ContactsStatusUpdatesColumns.CONCRETE_STATUS_ICON);
// Handle projections for Data-level statuses
addProjection(sDataProjectionMap, Data.PRESENCE,
Tables.PRESENCE + "." + StatusUpdates.PRESENCE);
addProjection(sDataProjectionMap, Data.STATUS,
StatusUpdatesColumns.CONCRETE_STATUS);
addProjection(sDataProjectionMap, Data.STATUS_TIMESTAMP,
StatusUpdatesColumns.CONCRETE_STATUS_TIMESTAMP);
addProjection(sDataProjectionMap, Data.STATUS_RES_PACKAGE,
StatusUpdatesColumns.CONCRETE_STATUS_RES_PACKAGE);
addProjection(sDataProjectionMap, Data.STATUS_LABEL,
StatusUpdatesColumns.CONCRETE_STATUS_LABEL);
addProjection(sDataProjectionMap, Data.STATUS_ICON,
StatusUpdatesColumns.CONCRETE_STATUS_ICON);
// Projection map for data grouped by contact (not raw contact) and some data field(s)
sDistinctDataProjectionMap = new HashMap<String, String>();
sDistinctDataProjectionMap.put(Data._ID,
"MIN(" + Data._ID + ") AS " + Data._ID);
sDistinctDataProjectionMap.put(Data.DATA_VERSION, Data.DATA_VERSION);
sDistinctDataProjectionMap.put(Data.IS_PRIMARY, Data.IS_PRIMARY);
sDistinctDataProjectionMap.put(Data.IS_SUPER_PRIMARY, Data.IS_SUPER_PRIMARY);
sDistinctDataProjectionMap.put(Data.RES_PACKAGE, Data.RES_PACKAGE);
sDistinctDataProjectionMap.put(Data.MIMETYPE, Data.MIMETYPE);
sDistinctDataProjectionMap.put(Data.DATA1, Data.DATA1);
sDistinctDataProjectionMap.put(Data.DATA2, Data.DATA2);
sDistinctDataProjectionMap.put(Data.DATA3, Data.DATA3);
sDistinctDataProjectionMap.put(Data.DATA4, Data.DATA4);
sDistinctDataProjectionMap.put(Data.DATA5, Data.DATA5);
sDistinctDataProjectionMap.put(Data.DATA6, Data.DATA6);
sDistinctDataProjectionMap.put(Data.DATA7, Data.DATA7);
sDistinctDataProjectionMap.put(Data.DATA8, Data.DATA8);
sDistinctDataProjectionMap.put(Data.DATA9, Data.DATA9);
sDistinctDataProjectionMap.put(Data.DATA10, Data.DATA10);
sDistinctDataProjectionMap.put(Data.DATA11, Data.DATA11);
sDistinctDataProjectionMap.put(Data.DATA12, Data.DATA12);
sDistinctDataProjectionMap.put(Data.DATA13, Data.DATA13);
sDistinctDataProjectionMap.put(Data.DATA14, Data.DATA14);
sDistinctDataProjectionMap.put(Data.DATA15, Data.DATA15);
sDistinctDataProjectionMap.put(Data.SYNC1, Data.SYNC1);
sDistinctDataProjectionMap.put(Data.SYNC2, Data.SYNC2);
sDistinctDataProjectionMap.put(Data.SYNC3, Data.SYNC3);
sDistinctDataProjectionMap.put(Data.SYNC4, Data.SYNC4);
sDistinctDataProjectionMap.put(RawContacts.CONTACT_ID, RawContacts.CONTACT_ID);
sDistinctDataProjectionMap.put(Contacts.LOOKUP_KEY, Contacts.LOOKUP_KEY);
sDistinctDataProjectionMap.put(Contacts.DISPLAY_NAME, Contacts.DISPLAY_NAME);
sDistinctDataProjectionMap.put(Contacts.CUSTOM_RINGTONE, Contacts.CUSTOM_RINGTONE);
sDistinctDataProjectionMap.put(Contacts.SEND_TO_VOICEMAIL, Contacts.SEND_TO_VOICEMAIL);
sDistinctDataProjectionMap.put(Contacts.LAST_TIME_CONTACTED, Contacts.LAST_TIME_CONTACTED);
sDistinctDataProjectionMap.put(Contacts.TIMES_CONTACTED, Contacts.TIMES_CONTACTED);
sDistinctDataProjectionMap.put(Contacts.STARRED, Contacts.STARRED);
sDistinctDataProjectionMap.put(Contacts.PHOTO_ID, Contacts.PHOTO_ID);
sDistinctDataProjectionMap.put(Contacts.IN_VISIBLE_GROUP, Contacts.IN_VISIBLE_GROUP);
sDistinctDataProjectionMap.put(GroupMembership.GROUP_SOURCE_ID,
GroupMembership.GROUP_SOURCE_ID);
// Handle projections for Contacts-level statuses
addProjection(sDistinctDataProjectionMap, Contacts.CONTACT_PRESENCE,
Tables.AGGREGATED_PRESENCE + "." + StatusUpdates.PRESENCE);
addProjection(sDistinctDataProjectionMap, Contacts.CONTACT_STATUS,
ContactsStatusUpdatesColumns.CONCRETE_STATUS);
addProjection(sDistinctDataProjectionMap, Contacts.CONTACT_STATUS_TIMESTAMP,
ContactsStatusUpdatesColumns.CONCRETE_STATUS_TIMESTAMP);
addProjection(sDistinctDataProjectionMap, Contacts.CONTACT_STATUS_RES_PACKAGE,
ContactsStatusUpdatesColumns.CONCRETE_STATUS_RES_PACKAGE);
addProjection(sDistinctDataProjectionMap, Contacts.CONTACT_STATUS_LABEL,
ContactsStatusUpdatesColumns.CONCRETE_STATUS_LABEL);
addProjection(sDistinctDataProjectionMap, Contacts.CONTACT_STATUS_ICON,
ContactsStatusUpdatesColumns.CONCRETE_STATUS_ICON);
// Handle projections for Data-level statuses
addProjection(sDistinctDataProjectionMap, Data.PRESENCE,
Tables.PRESENCE + "." + StatusUpdates.PRESENCE);
addProjection(sDistinctDataProjectionMap, Data.STATUS,
StatusUpdatesColumns.CONCRETE_STATUS);
addProjection(sDistinctDataProjectionMap, Data.STATUS_TIMESTAMP,
StatusUpdatesColumns.CONCRETE_STATUS_TIMESTAMP);
addProjection(sDistinctDataProjectionMap, Data.STATUS_RES_PACKAGE,
StatusUpdatesColumns.CONCRETE_STATUS_RES_PACKAGE);
addProjection(sDistinctDataProjectionMap, Data.STATUS_LABEL,
StatusUpdatesColumns.CONCRETE_STATUS_LABEL);
addProjection(sDistinctDataProjectionMap, Data.STATUS_ICON,
StatusUpdatesColumns.CONCRETE_STATUS_ICON);
sPhoneLookupProjectionMap = new HashMap<String, String>();
sPhoneLookupProjectionMap.put(PhoneLookup._ID,
ContactsColumns.CONCRETE_ID + " AS " + PhoneLookup._ID);
sPhoneLookupProjectionMap.put(PhoneLookup.LOOKUP_KEY,
Contacts.LOOKUP_KEY + " AS " + PhoneLookup.LOOKUP_KEY);
sPhoneLookupProjectionMap.put(PhoneLookup.DISPLAY_NAME,
ContactsColumns.CONCRETE_DISPLAY_NAME + " AS " + PhoneLookup.DISPLAY_NAME);
sPhoneLookupProjectionMap.put(PhoneLookup.LAST_TIME_CONTACTED,
ContactsColumns.CONCRETE_LAST_TIME_CONTACTED
+ " AS " + PhoneLookup.LAST_TIME_CONTACTED);
sPhoneLookupProjectionMap.put(PhoneLookup.TIMES_CONTACTED,
ContactsColumns.CONCRETE_TIMES_CONTACTED + " AS " + PhoneLookup.TIMES_CONTACTED);
sPhoneLookupProjectionMap.put(PhoneLookup.STARRED,
ContactsColumns.CONCRETE_STARRED + " AS " + PhoneLookup.STARRED);
sPhoneLookupProjectionMap.put(PhoneLookup.IN_VISIBLE_GROUP,
Contacts.IN_VISIBLE_GROUP + " AS " + PhoneLookup.IN_VISIBLE_GROUP);
sPhoneLookupProjectionMap.put(PhoneLookup.PHOTO_ID,
Contacts.PHOTO_ID + " AS " + PhoneLookup.PHOTO_ID);
sPhoneLookupProjectionMap.put(PhoneLookup.CUSTOM_RINGTONE,
ContactsColumns.CONCRETE_CUSTOM_RINGTONE + " AS " + PhoneLookup.CUSTOM_RINGTONE);
sPhoneLookupProjectionMap.put(PhoneLookup.HAS_PHONE_NUMBER,
Contacts.HAS_PHONE_NUMBER + " AS " + PhoneLookup.HAS_PHONE_NUMBER);
sPhoneLookupProjectionMap.put(PhoneLookup.SEND_TO_VOICEMAIL,
ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL
+ " AS " + PhoneLookup.SEND_TO_VOICEMAIL);
sPhoneLookupProjectionMap.put(PhoneLookup.NUMBER,
Phone.NUMBER + " AS " + PhoneLookup.NUMBER);
sPhoneLookupProjectionMap.put(PhoneLookup.TYPE,
Phone.TYPE + " AS " + PhoneLookup.TYPE);
sPhoneLookupProjectionMap.put(PhoneLookup.LABEL,
Phone.LABEL + " AS " + PhoneLookup.LABEL);
// Groups projection map
columns = new HashMap<String, String>();
columns.put(Groups._ID, Groups._ID);
columns.put(Groups.ACCOUNT_NAME, Groups.ACCOUNT_NAME);
columns.put(Groups.ACCOUNT_TYPE, Groups.ACCOUNT_TYPE);
columns.put(Groups.SOURCE_ID, Groups.SOURCE_ID);
columns.put(Groups.DIRTY, Groups.DIRTY);
columns.put(Groups.VERSION, Groups.VERSION);
columns.put(Groups.RES_PACKAGE, Groups.RES_PACKAGE);
columns.put(Groups.TITLE, Groups.TITLE);
columns.put(Groups.TITLE_RES, Groups.TITLE_RES);
columns.put(Groups.GROUP_VISIBLE, Groups.GROUP_VISIBLE);
columns.put(Groups.SYSTEM_ID, Groups.SYSTEM_ID);
columns.put(Groups.DELETED, Groups.DELETED);
columns.put(Groups.NOTES, Groups.NOTES);
columns.put(Groups.SHOULD_SYNC, Groups.SHOULD_SYNC);
columns.put(Groups.SYNC1, Groups.SYNC1);
columns.put(Groups.SYNC2, Groups.SYNC2);
columns.put(Groups.SYNC3, Groups.SYNC3);
columns.put(Groups.SYNC4, Groups.SYNC4);
sGroupsProjectionMap = columns;
// RawContacts and groups projection map
columns = new HashMap<String, String>();
columns.putAll(sGroupsProjectionMap);
columns.put(Groups.SUMMARY_COUNT, "(SELECT COUNT(DISTINCT " + ContactsColumns.CONCRETE_ID
+ ") FROM " + Tables.DATA_JOIN_MIMETYPES_RAW_CONTACTS_CONTACTS + " WHERE "
+ Clauses.MIMETYPE_IS_GROUP_MEMBERSHIP + " AND " + Clauses.BELONGS_TO_GROUP
+ ") AS " + Groups.SUMMARY_COUNT);
columns.put(Groups.SUMMARY_WITH_PHONES, "(SELECT COUNT(DISTINCT "
+ ContactsColumns.CONCRETE_ID + ") FROM "
+ Tables.DATA_JOIN_MIMETYPES_RAW_CONTACTS_CONTACTS + " WHERE "
+ Clauses.MIMETYPE_IS_GROUP_MEMBERSHIP + " AND " + Clauses.BELONGS_TO_GROUP
+ " AND " + Contacts.HAS_PHONE_NUMBER + ") AS " + Groups.SUMMARY_WITH_PHONES);
sGroupsSummaryProjectionMap = columns;
// Aggregate exception projection map
columns = new HashMap<String, String>();
columns.put(AggregationExceptionColumns._ID, Tables.AGGREGATION_EXCEPTIONS + "._id AS _id");
columns.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE);
columns.put(AggregationExceptions.RAW_CONTACT_ID1, AggregationExceptions.RAW_CONTACT_ID1);
columns.put(AggregationExceptions.RAW_CONTACT_ID2, AggregationExceptions.RAW_CONTACT_ID2);
sAggregationExceptionsProjectionMap = columns;
// Settings projection map
columns = new HashMap<String, String>();
columns.put(Settings.ACCOUNT_NAME, Settings.ACCOUNT_NAME);
columns.put(Settings.ACCOUNT_TYPE, Settings.ACCOUNT_TYPE);
columns.put(Settings.UNGROUPED_VISIBLE, Settings.UNGROUPED_VISIBLE);
columns.put(Settings.SHOULD_SYNC, Settings.SHOULD_SYNC);
columns.put(Settings.ANY_UNSYNCED, "(CASE WHEN MIN(" + Settings.SHOULD_SYNC
+ ",(SELECT (CASE WHEN MIN(" + Groups.SHOULD_SYNC + ") IS NULL THEN 1 ELSE MIN("
+ Groups.SHOULD_SYNC + ") END) FROM " + Tables.GROUPS + " WHERE "
+ GroupsColumns.CONCRETE_ACCOUNT_NAME + "=" + SettingsColumns.CONCRETE_ACCOUNT_NAME
+ " AND " + GroupsColumns.CONCRETE_ACCOUNT_TYPE + "="
+ SettingsColumns.CONCRETE_ACCOUNT_TYPE + "))=0 THEN 1 ELSE 0 END) AS "
+ Settings.ANY_UNSYNCED);
columns.put(Settings.UNGROUPED_COUNT, "(SELECT COUNT(*) FROM (SELECT 1 FROM "
+ Tables.SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS + " GROUP BY "
+ Clauses.GROUP_BY_ACCOUNT_CONTACT_ID + " HAVING " + Clauses.HAVING_NO_GROUPS
+ ")) AS " + Settings.UNGROUPED_COUNT);
columns.put(Settings.UNGROUPED_WITH_PHONES, "(SELECT COUNT(*) FROM (SELECT 1 FROM "
+ Tables.SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS + " WHERE "
+ Contacts.HAS_PHONE_NUMBER + " GROUP BY " + Clauses.GROUP_BY_ACCOUNT_CONTACT_ID
+ " HAVING " + Clauses.HAVING_NO_GROUPS + ")) AS "
+ Settings.UNGROUPED_WITH_PHONES);
sSettingsProjectionMap = columns;
columns = new HashMap<String, String>();
columns.put(PresenceColumns.RAW_CONTACT_ID, PresenceColumns.RAW_CONTACT_ID);
columns.put(StatusUpdates.DATA_ID,
DataColumns.CONCRETE_ID + " AS " + StatusUpdates.DATA_ID);
columns.put(StatusUpdates.IM_ACCOUNT, StatusUpdates.IM_ACCOUNT);
columns.put(StatusUpdates.IM_HANDLE, StatusUpdates.IM_HANDLE);
columns.put(StatusUpdates.PROTOCOL, StatusUpdates.PROTOCOL);
// We cannot allow a null in the custom protocol field, because SQLite3 does not
// properly enforce uniqueness of null values
columns.put(StatusUpdates.CUSTOM_PROTOCOL, "(CASE WHEN " + StatusUpdates.CUSTOM_PROTOCOL
+ "='' THEN NULL ELSE " + StatusUpdates.CUSTOM_PROTOCOL + " END) AS "
+ StatusUpdates.CUSTOM_PROTOCOL);
columns.put(StatusUpdates.PRESENCE, StatusUpdates.PRESENCE);
columns.put(StatusUpdat