FileDocCategorySizeDatePackage
ImProvider.javaAPI DocAndroid 1.5 API116628Wed May 06 22:42:48 BST 2009com.android.providers.im

ImProvider

public class ImProvider extends android.content.ContentProvider
A content provider for IM

Fields Summary
private static final String
LOG_TAG
private static final boolean
DBG
private static final String
AUTHORITY
private static final boolean
USE_CONTACT_PRESENCE_TRIGGER
private static final String
TABLE_ACCOUNTS
private static final String
TABLE_PROVIDERS
private static final String
TABLE_PROVIDER_SETTINGS
private static final String
TABLE_CONTACTS
private static final String
TABLE_CONTACTS_ETAG
private static final String
TABLE_BLOCKED_LIST
private static final String
TABLE_CONTACT_LIST
private static final String
TABLE_INVITATIONS
private static final String
TABLE_GROUP_MEMBERS
private static final String
TABLE_GROUP_MESSAGES
private static final String
TABLE_PRESENCE
private static final String
USERNAME
private static final String
TABLE_CHATS
private static final String
TABLE_AVATARS
private static final String
TABLE_SESSION_COOKIES
private static final String
TABLE_MESSAGES
private static final String
TABLE_OUTGOING_RMQ_MESSAGES
private static final String
TABLE_LAST_RMQ_ID
private static final String
TABLE_ACCOUNT_STATUS
private static final String
TABLE_BRANDING_RESOURCE_MAP_CACHE
private static final String
DATABASE_NAME
private static final int
DATABASE_VERSION
protected static final int
MATCH_PROVIDERS
protected static final int
MATCH_PROVIDERS_BY_ID
protected static final int
MATCH_PROVIDERS_WITH_ACCOUNT
protected static final int
MATCH_ACCOUNTS
protected static final int
MATCH_ACCOUNTS_BY_ID
protected static final int
MATCH_CONTACTS
protected static final int
MATCH_CONTACTS_JOIN_PRESENCE
protected static final int
MATCH_CONTACTS_BAREBONE
protected static final int
MATCH_CHATTING_CONTACTS
protected static final int
MATCH_CONTACTS_BY_PROVIDER
protected static final int
MATCH_CHATTING_CONTACTS_BY_PROVIDER
protected static final int
MATCH_NO_CHATTING_CONTACTS_BY_PROVIDER
protected static final int
MATCH_ONLINE_CONTACTS_BY_PROVIDER
protected static final int
MATCH_OFFLINE_CONTACTS_BY_PROVIDER
protected static final int
MATCH_CONTACT
protected static final int
MATCH_CONTACTS_BULK
protected static final int
MATCH_ONLINE_CONTACT_COUNT
protected static final int
MATCH_BLOCKED_CONTACTS
protected static final int
MATCH_CONTACTLISTS
protected static final int
MATCH_CONTACTLISTS_BY_PROVIDER
protected static final int
MATCH_CONTACTLIST
protected static final int
MATCH_BLOCKEDLIST
protected static final int
MATCH_BLOCKEDLIST_BY_PROVIDER
protected static final int
MATCH_CONTACTS_ETAGS
protected static final int
MATCH_CONTACTS_ETAG
protected static final int
MATCH_PRESENCE
protected static final int
MATCH_PRESENCE_ID
protected static final int
MATCH_PRESENCE_BY_ACCOUNT
protected static final int
MATCH_PRESENCE_SEED_BY_ACCOUNT
protected static final int
MATCH_PRESENCE_BULK
protected static final int
MATCH_MESSAGES
protected static final int
MATCH_MESSAGES_BY_CONTACT
protected static final int
MATCH_MESSAGE
protected static final int
MATCH_GROUP_MESSAGES
protected static final int
MATCH_GROUP_MESSAGE_BY
protected static final int
MATCH_GROUP_MESSAGE
protected static final int
MATCH_GROUP_MEMBERS
protected static final int
MATCH_GROUP_MEMBERS_BY_GROUP
protected static final int
MATCH_AVATARS
protected static final int
MATCH_AVATAR
protected static final int
MATCH_AVATAR_BY_PROVIDER
protected static final int
MATCH_CHATS
protected static final int
MATCH_CHATS_BY_ACCOUNT
protected static final int
MATCH_CHATS_ID
protected static final int
MATCH_SESSIONS
protected static final int
MATCH_SESSIONS_BY_PROVIDER
protected static final int
MATCH_PROVIDER_SETTINGS
protected static final int
MATCH_PROVIDER_SETTINGS_BY_ID
protected static final int
MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME
protected static final int
MATCH_INVITATIONS
protected static final int
MATCH_INVITATION
protected static final int
MATCH_OUTGOING_RMQ_MESSAGES
protected static final int
MATCH_OUTGOING_RMQ_MESSAGE
protected static final int
MATCH_OUTGOING_HIGHEST_RMQ_ID
protected static final int
MATCH_LAST_RMQ_ID
protected static final int
MATCH_ACCOUNTS_STATUS
protected static final int
MATCH_ACCOUNT_STATUS
protected static final int
MATCH_BRANDING_RESOURCE_MAP_CACHE
protected final android.content.UriMatcher
mUrlMatcher
private final String
mTransientDbName
private static final HashMap
sProviderAccountsProjectionMap
private static final HashMap
sContactsProjectionMap
private static final HashMap
sContactListProjectionMap
private static final HashMap
sBlockedListProjectionMap
private static final String
PROVIDER_JOIN_ACCOUNT_TABLE
private static final String
CONTACT_JOIN_PRESENCE_TABLE
private static final String
CONTACT_JOIN_PRESENCE_CHAT_TABLE
private static final String
CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE
private static final String
BLOCKEDLIST_JOIN_AVATAR_TABLE
private static final String
NON_BLOCKED_CONTACTS_WHERE_CLAUSE
The where clause for filtering out blocked contacts
private static final String
BLOCKED_CONTACTS_WHERE_CLAUSE
private static final String
CONTACT_ID
private static final String
PRESENCE_CONTACT_ID
protected android.database.sqlite.SQLiteOpenHelper
mOpenHelper
private final String
mDatabaseName
private final int
mDatabaseVersion
private final String[]
BACKFILL_PROJECTION
private final String[]
FIND_SHORTCUT_PROJECTION
private static final String[]
CONTACT_ID_PROJECTION
private static final int
COLUMN_ID
private static final String
CONTACTS_WITH_NO_PRESENCE_SELECTION
private String[]
mQueryContactPresenceSelectionArgs
private static final String
DELETE_PRESENCE_SELECTION
manual trigger for deleting contacts
private static final String
CHATS_CONTACT_ID
private static final String
DELETE_CHATS_SELECTION
private static final String
GROUP_MEMBER_ID
private static final String
DELETE_GROUP_MEMBER_SELECTION
private static final String
GROUP_MESSAGES_ID
private static final String
DELETE_GROUP_MESSAGES_SELECTION
Constructors Summary
public ImProvider()

        sProviderAccountsProjectionMap = new HashMap<String, String>();
        sProviderAccountsProjectionMap.put(Im.Provider._ID,
                "providers._id AS _id");
        sProviderAccountsProjectionMap.put(Im.Provider._COUNT,
                "COUNT(*) AS _account");
        sProviderAccountsProjectionMap.put(Im.Provider.NAME,
                "providers.name AS name");
        sProviderAccountsProjectionMap.put(Im.Provider.FULLNAME,
                "providers.fullname AS fullname");
        sProviderAccountsProjectionMap.put(Im.Provider.CATEGORY,
                "providers.category AS category");
        sProviderAccountsProjectionMap.put(Im.Provider.ACTIVE_ACCOUNT_ID,
                "accounts._id AS account_id");
        sProviderAccountsProjectionMap.put(Im.Provider.ACTIVE_ACCOUNT_USERNAME,
                "accounts.username AS account_username");
        sProviderAccountsProjectionMap.put(Im.Provider.ACTIVE_ACCOUNT_PW,
                "accounts.pw AS account_pw");
        sProviderAccountsProjectionMap.put(Im.Provider.ACTIVE_ACCOUNT_LOCKED,
                "accounts.locked AS account_locked");
        sProviderAccountsProjectionMap.put(Im.Provider.ACCOUNT_PRESENCE_STATUS,
                "accountStatus.presenceStatus AS account_presenceStatus");
        sProviderAccountsProjectionMap.put(Im.Provider.ACCOUNT_CONNECTION_STATUS,
                "accountStatus.connStatus AS account_connStatus");

        // contacts projection map
        sContactsProjectionMap = new HashMap<String, String>();

        // Base column
        sContactsProjectionMap.put(Im.Contacts._ID, "contacts._id AS _id");
        sContactsProjectionMap.put(Im.Contacts._COUNT, "COUNT(*) AS _count");

        // contacts column
        sContactsProjectionMap.put(Im.Contacts._ID, "contacts._id as _id");
        sContactsProjectionMap.put(Im.Contacts.USERNAME, "contacts.username as username");
        sContactsProjectionMap.put(Im.Contacts.NICKNAME, "contacts.nickname as nickname");
        sContactsProjectionMap.put(Im.Contacts.PROVIDER, "contacts.provider as provider");
        sContactsProjectionMap.put(Im.Contacts.ACCOUNT, "contacts.account as account");
        sContactsProjectionMap.put(Im.Contacts.CONTACTLIST, "contacts.contactList as contactList");
        sContactsProjectionMap.put(Im.Contacts.TYPE, "contacts.type as type");
        sContactsProjectionMap.put(Im.Contacts.SUBSCRIPTION_STATUS,
                "contacts.subscriptionStatus as subscriptionStatus");
        sContactsProjectionMap.put(Im.Contacts.SUBSCRIPTION_TYPE,
                "contacts.subscriptionType as subscriptionType");
        sContactsProjectionMap.put(Im.Contacts.QUICK_CONTACT, "contacts.qc as qc");
        sContactsProjectionMap.put(Im.Contacts.REJECTED, "contacts.rejected as rejected");

        // Presence columns
        sContactsProjectionMap.put(Im.Presence.CONTACT_ID,
                "presence.contact_id AS contact_id");
        sContactsProjectionMap.put(Im.Contacts.PRESENCE_STATUS,
                "presence.mode AS mode");
        sContactsProjectionMap.put(Im.Contacts.PRESENCE_CUSTOM_STATUS,
                "presence.status AS status");
        sContactsProjectionMap.put(Im.Contacts.CLIENT_TYPE,
                "presence.client_type AS client_type");

        // Chats columns
        sContactsProjectionMap.put(Im.Contacts.CHATS_CONTACT,
                "chats.contact_id AS chats_contact_id");
        sContactsProjectionMap.put(Im.Chats.JID_RESOURCE,
                "chats.jid_resource AS jid_resource");
        sContactsProjectionMap.put(Im.Chats.GROUP_CHAT,
                "chats.groupchat AS groupchat");
        sContactsProjectionMap.put(Im.Contacts.LAST_UNREAD_MESSAGE,
                "chats.last_unread_message AS last_unread_message");
        sContactsProjectionMap.put(Im.Contacts.LAST_MESSAGE_DATE,
                "chats.last_message_date AS last_message_date");
        sContactsProjectionMap.put(Im.Contacts.UNSENT_COMPOSED_MESSAGE,
                "chats.unsent_composed_message AS unsent_composed_message");
        sContactsProjectionMap.put(Im.Contacts.SHORTCUT, "chats.SHORTCUT AS shortcut");

        // Avatars columns
        sContactsProjectionMap.put(Im.Contacts.AVATAR_HASH, "avatars.hash AS avatars_hash");
        sContactsProjectionMap.put(Im.Contacts.AVATAR_DATA, "avatars.data AS avatars_data");

        // contactList projection map
        sContactListProjectionMap = new HashMap<String, String>();
        sContactListProjectionMap.put(Im.ContactList._ID,
                "contactList._id AS _id");
        sContactListProjectionMap.put(Im.ContactList._COUNT,
                "COUNT(*) AS _count");
        sContactListProjectionMap.put(Im.ContactList.NAME, "name");
        sContactListProjectionMap.put(Im.ContactList.PROVIDER, "provider");
        sContactListProjectionMap.put(Im.ContactList.ACCOUNT, "account");

        // blockedList projection map
        sBlockedListProjectionMap = new HashMap<String, String>();
        sBlockedListProjectionMap.put(Im.BlockedList._ID,
                "blockedList._id AS _id");
        sBlockedListProjectionMap.put(Im.BlockedList._COUNT,
                "COUNT(*) AS _count");
        sBlockedListProjectionMap.put(Im.BlockedList.USERNAME, "username");
        sBlockedListProjectionMap.put(Im.BlockedList.NICKNAME, "nickname");
        sBlockedListProjectionMap.put(Im.BlockedList.PROVIDER, "provider");
        sBlockedListProjectionMap.put(Im.BlockedList.ACCOUNT, "account");
        sBlockedListProjectionMap.put(Im.BlockedList.AVATAR_DATA,
                "avatars.data AS avatars_data");
    
        this(AUTHORITY, DATABASE_NAME, DATABASE_VERSION);
    
protected ImProvider(String authority, String dbName, int dbVersion)

        mDatabaseName = dbName;
        mDatabaseVersion = dbVersion;

        mTransientDbName = "transient_" + dbName.replace(".", "_");

        mUrlMatcher.addURI(authority, "providers", MATCH_PROVIDERS);
        mUrlMatcher.addURI(authority, "providers/#", MATCH_PROVIDERS_BY_ID);
        mUrlMatcher.addURI(authority, "providers/account", MATCH_PROVIDERS_WITH_ACCOUNT);

        mUrlMatcher.addURI(authority, "accounts", MATCH_ACCOUNTS);
        mUrlMatcher.addURI(authority, "accounts/#", MATCH_ACCOUNTS_BY_ID);

        mUrlMatcher.addURI(authority, "contacts", MATCH_CONTACTS);
        mUrlMatcher.addURI(authority, "contactsWithPresence", MATCH_CONTACTS_JOIN_PRESENCE);
        mUrlMatcher.addURI(authority, "contactsBarebone", MATCH_CONTACTS_BAREBONE);
        mUrlMatcher.addURI(authority, "contacts/#/#", MATCH_CONTACTS_BY_PROVIDER);
        mUrlMatcher.addURI(authority, "contacts/chatting", MATCH_CHATTING_CONTACTS);
        mUrlMatcher.addURI(authority, "contacts/chatting/#/#", MATCH_CHATTING_CONTACTS_BY_PROVIDER);
        mUrlMatcher.addURI(authority, "contacts/online/#/#", MATCH_ONLINE_CONTACTS_BY_PROVIDER);
        mUrlMatcher.addURI(authority, "contacts/offline/#/#", MATCH_OFFLINE_CONTACTS_BY_PROVIDER);
        mUrlMatcher.addURI(authority, "contacts/#", MATCH_CONTACT);
        mUrlMatcher.addURI(authority, "contacts/blocked", MATCH_BLOCKED_CONTACTS);
        mUrlMatcher.addURI(authority, "bulk_contacts", MATCH_CONTACTS_BULK);
        mUrlMatcher.addURI(authority, "contacts/onlineCount", MATCH_ONLINE_CONTACT_COUNT);

        mUrlMatcher.addURI(authority, "contactLists", MATCH_CONTACTLISTS);
        mUrlMatcher.addURI(authority, "contactLists/#/#", MATCH_CONTACTLISTS_BY_PROVIDER);
        mUrlMatcher.addURI(authority, "contactLists/#", MATCH_CONTACTLIST);
        mUrlMatcher.addURI(authority, "blockedList", MATCH_BLOCKEDLIST);
        mUrlMatcher.addURI(authority, "blockedList/#/#", MATCH_BLOCKEDLIST_BY_PROVIDER);

        mUrlMatcher.addURI(authority, "contactsEtag", MATCH_CONTACTS_ETAGS);
        mUrlMatcher.addURI(authority, "contactsEtag/#", MATCH_CONTACTS_ETAG);

        mUrlMatcher.addURI(authority, "presence", MATCH_PRESENCE);
        mUrlMatcher.addURI(authority, "presence/#", MATCH_PRESENCE_ID);
        mUrlMatcher.addURI(authority, "presence/account/#", MATCH_PRESENCE_BY_ACCOUNT);
        mUrlMatcher.addURI(authority, "seed_presence/account/#", MATCH_PRESENCE_SEED_BY_ACCOUNT);
        mUrlMatcher.addURI(authority, "bulk_presence", MATCH_PRESENCE_BULK);

        mUrlMatcher.addURI(authority, "messages", MATCH_MESSAGES);
        mUrlMatcher.addURI(authority, "messagesBy/#/#/*", MATCH_MESSAGES_BY_CONTACT);
        mUrlMatcher.addURI(authority, "messages/#", MATCH_MESSAGE);

        mUrlMatcher.addURI(authority, "groupMessages", MATCH_GROUP_MESSAGES);
        mUrlMatcher.addURI(authority, "groupMessagesBy/#", MATCH_GROUP_MESSAGE_BY);
        mUrlMatcher.addURI(authority, "groupMessages/#", MATCH_GROUP_MESSAGE);
        mUrlMatcher.addURI(authority, "groupMembers", MATCH_GROUP_MEMBERS);
        mUrlMatcher.addURI(authority, "groupMembers/#", MATCH_GROUP_MEMBERS_BY_GROUP);

        mUrlMatcher.addURI(authority, "avatars", MATCH_AVATARS);
        mUrlMatcher.addURI(authority, "avatars/#", MATCH_AVATAR);
        mUrlMatcher.addURI(authority, "avatarsBy/#/#", MATCH_AVATAR_BY_PROVIDER);
        mUrlMatcher.addURI(authority, "chats", MATCH_CHATS);
        mUrlMatcher.addURI(authority, "chats/account/#", MATCH_CHATS_BY_ACCOUNT);
        mUrlMatcher.addURI(authority, "chats/#", MATCH_CHATS_ID);

        mUrlMatcher.addURI(authority, "sessionCookies", MATCH_SESSIONS);
        mUrlMatcher.addURI(authority, "sessionCookiesBy/#/#", MATCH_SESSIONS_BY_PROVIDER);
        mUrlMatcher.addURI(authority, "providerSettings", MATCH_PROVIDER_SETTINGS);
        mUrlMatcher.addURI(authority, "providerSettings/#", MATCH_PROVIDER_SETTINGS_BY_ID);
        mUrlMatcher.addURI(authority, "providerSettings/#/*",
                MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME);

        mUrlMatcher.addURI(authority, "invitations", MATCH_INVITATIONS);
        mUrlMatcher.addURI(authority, "invitations/#", MATCH_INVITATION);

        mUrlMatcher.addURI(authority, "outgoingRmqMessages", MATCH_OUTGOING_RMQ_MESSAGES);
        mUrlMatcher.addURI(authority, "outgoingRmqMessages/#", MATCH_OUTGOING_RMQ_MESSAGE);
        mUrlMatcher.addURI(authority, "outgoingHighestRmqId", MATCH_OUTGOING_HIGHEST_RMQ_ID);
        mUrlMatcher.addURI(authority, "lastRmqId", MATCH_LAST_RMQ_ID);

        mUrlMatcher.addURI(authority, "accountStatus", MATCH_ACCOUNTS_STATUS);
        mUrlMatcher.addURI(authority, "accountStatus/#", MATCH_ACCOUNT_STATUS);

        mUrlMatcher.addURI(authority, "brandingResMapCache", MATCH_BRANDING_RESOURCE_MAP_CACHE);
    
Methods Summary
private voidaddToQuickSwitch(long newRow)

        //  Since there are fewer than 10, there must be an empty slot.  Let's find it.
        int slot = findEmptyQuickSwitchSlot();

        if (slot == -1) {
            return;
        }

        updateSlotForChat(newRow, slot);
    
private voidappendValuesFromUrl(android.content.ContentValues values, android.net.Uri url, java.lang.String columns)

        if(url.getPathSegments().size() <= columns.length) {
            throw new IllegalArgumentException("Not enough values in url");
        }
        for(int i = 0; i < columns.length; i++){
            if(values.containsKey(columns[i])){
                throw new UnsupportedOperationException("Cannot override the value for " + columns[i]);
            }
            values.put(columns[i], decodeURLSegment(url.getPathSegments().get(i + 1)));
        }
    
private static voidappendWhere(java.lang.StringBuilder where, java.lang.String columnName, java.lang.String condition, java.lang.Object value)

        if (where.length() > 0) {
            where.append(" AND ");
        }
        where.append(columnName).append(condition);
        if(value != null) {
            DatabaseUtils.appendValueToSql(where, value);
        }
    
private static voidappendWhere(java.lang.StringBuilder where, java.lang.String clause)

        if (where.length() > 0) {
            where.append(" AND ");
        }
        where.append(clause);
    
private voidbackfillQuickSwitchSlots()

        //  Find all the chats without a quick switch slot, and order
        Cursor c = query(Im.Chats.CONTENT_URI,
            BACKFILL_PROJECTION,
            Im.Chats.SHORTCUT + "=-1", null, Im.Chats.LAST_MESSAGE_DATE + " DESC");

        try {
            if (c.getCount() < 1) {
                return;
            }
        
            int slot = findEmptyQuickSwitchSlot();
        
            if (slot != -1) {
                c.moveToFirst();
            
                long id = c.getLong(c.getColumnIndex(Im.Chats._ID));
            
                updateSlotForChat(id, slot);
            }
        } finally {
            c.close();
        }
    
private voidbuildQueryContactsByProvider(android.database.sqlite.SQLiteQueryBuilder qb, java.lang.StringBuilder whereClause, android.net.Uri url)

        qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE);
        qb.setProjectionMap(sContactsProjectionMap);
        // we don't really need the provider id in query. account id
        // is enough.
        appendWhere(whereClause, Im.Contacts.ACCOUNT, "=", url.getLastPathSegment());
    
private static java.lang.StringdecodeURLSegment(java.lang.String segment)

        try {
            return URLDecoder.decode(segment, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            // impossible
            return segment;
        }
    
public final intdelete(android.net.Uri url, java.lang.String selection, java.lang.String[] selectionArgs)

        int result;
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        db.beginTransaction();
        try {
            result = deleteInternal(url, selection, selectionArgs);
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
        if (result > 0) {
            getContext().getContentResolver()
                    .notifyChange(url, null /* observer */, false /* sync */);
        }
        return result;
    
public intdeleteInternal(android.net.Uri url, java.lang.String userWhere, java.lang.String[] whereArgs)

        String tableToChange;
        String idColumnName = null;
        String changedItemId = null;

        StringBuilder whereClause = new StringBuilder();
        if(userWhere != null) {
            whereClause.append(userWhere);
        }

        boolean notifyMessagesContentUri = false;
        boolean notifyGroupMessagesContentUri = false;
        boolean notifyContactListContentUri = false;
        boolean notifyProviderAccountContentUri = false;
        int match = mUrlMatcher.match(url);

        boolean contactDeleted = false;
        long deletedContactId = 0;

        boolean backfillQuickSwitchSlots = false;
        
        switch (match) {
            case MATCH_PROVIDERS:
                tableToChange = TABLE_PROVIDERS;
                notifyProviderAccountContentUri = true;
                break;

            case MATCH_ACCOUNTS_BY_ID:
                changedItemId = url.getPathSegments().get(1);
                // fall through
            case MATCH_ACCOUNTS:
                tableToChange = TABLE_ACCOUNTS;
                notifyProviderAccountContentUri = true;
                break;

            case MATCH_ACCOUNT_STATUS:
                changedItemId = url.getPathSegments().get(1);
                // fall through
            case MATCH_ACCOUNTS_STATUS:
                tableToChange = TABLE_ACCOUNT_STATUS;
                notifyProviderAccountContentUri = true;
                break;

            case MATCH_CONTACTS:
            case MATCH_CONTACTS_BAREBONE:
                tableToChange = TABLE_CONTACTS;
                contactDeleted = true;
                break;

            case MATCH_CONTACT:
                tableToChange = TABLE_CONTACTS;
                changedItemId = url.getPathSegments().get(1);

                try {
                    deletedContactId = Long.parseLong(changedItemId);
                } catch (NumberFormatException ex) {
                }

                contactDeleted = true;
                break;

            case MATCH_CONTACTS_BY_PROVIDER:
                tableToChange = TABLE_CONTACTS;
                appendWhere(whereClause, Im.Contacts.ACCOUNT, "=", url.getPathSegments().get(2));
                contactDeleted = true;
                break;

            case MATCH_CONTACTLISTS_BY_PROVIDER:
                appendWhere(whereClause, Im.ContactList.ACCOUNT, "=",
                        url.getPathSegments().get(2));
                // fall through
            case MATCH_CONTACTLISTS:
                tableToChange = TABLE_CONTACT_LIST;
                notifyContactListContentUri = true;
                break;

            case MATCH_CONTACTLIST:
                tableToChange = TABLE_CONTACT_LIST;
                changedItemId = url.getPathSegments().get(1);
                break;

            case MATCH_BLOCKEDLIST:
                tableToChange = TABLE_BLOCKED_LIST;
                break;

            case MATCH_BLOCKEDLIST_BY_PROVIDER:
                tableToChange = TABLE_BLOCKED_LIST;
                appendWhere(whereClause, Im.BlockedList.ACCOUNT, "=", url.getPathSegments().get(2));
                break;

            case MATCH_CONTACTS_ETAGS:
                tableToChange = TABLE_CONTACTS_ETAG;
                break;

            case MATCH_CONTACTS_ETAG:
                tableToChange = TABLE_CONTACTS_ETAG;
                changedItemId = url.getPathSegments().get(1);
                break;

            case MATCH_MESSAGES:
                tableToChange = TABLE_MESSAGES;
                break;

            case MATCH_MESSAGES_BY_CONTACT:
                tableToChange = TABLE_MESSAGES;
                appendWhere(whereClause, Im.Messages.ACCOUNT, "=",
                        url.getPathSegments().get(2));
                appendWhere(whereClause, Im.Messages.CONTACT, "=",
                    decodeURLSegment(url.getPathSegments().get(3)));
                notifyMessagesContentUri = true;
                break;

            case MATCH_MESSAGE:
                tableToChange = TABLE_MESSAGES;
                changedItemId = url.getPathSegments().get(1);
                notifyMessagesContentUri = true;
                break;

            case MATCH_GROUP_MEMBERS:
                tableToChange = TABLE_GROUP_MEMBERS;
                break;

            case MATCH_GROUP_MEMBERS_BY_GROUP:
                tableToChange = TABLE_GROUP_MEMBERS;
                appendWhere(whereClause, Im.GroupMembers.GROUP, "=", url.getPathSegments().get(1));
                break;

            case MATCH_GROUP_MESSAGES:
                tableToChange = TABLE_GROUP_MESSAGES;
                break;

            case MATCH_GROUP_MESSAGE_BY:
                tableToChange = TABLE_GROUP_MESSAGES;
                changedItemId = url.getPathSegments().get(1);
                idColumnName = Im.GroupMessages.GROUP;
                notifyGroupMessagesContentUri = true;
                break;

            case MATCH_GROUP_MESSAGE:
                tableToChange = TABLE_GROUP_MESSAGES;
                changedItemId = url.getPathSegments().get(1);
                notifyGroupMessagesContentUri = true;
                break;

            case MATCH_INVITATIONS:
                tableToChange = TABLE_INVITATIONS;
                break;

            case MATCH_INVITATION:
                tableToChange = TABLE_INVITATIONS;
                changedItemId = url.getPathSegments().get(1);
                break;

            case MATCH_AVATARS:
                tableToChange = TABLE_AVATARS;
                break;

            case MATCH_AVATAR:
                tableToChange = TABLE_AVATARS;
                changedItemId = url.getPathSegments().get(1);
                break;

            case MATCH_AVATAR_BY_PROVIDER:
                tableToChange = TABLE_AVATARS;
                changedItemId = url.getPathSegments().get(2);
                idColumnName = Im.Avatars.ACCOUNT;
                break;

            case MATCH_CHATS:
                tableToChange = TABLE_CHATS;
                backfillQuickSwitchSlots = true;
                break;

            case MATCH_CHATS_BY_ACCOUNT:
                tableToChange = TABLE_CHATS;

                if (whereClause.length() > 0) {
                    whereClause.append(" AND ");
                }
                whereClause.append(Im.Chats.CONTACT_ID);
                whereClause.append(" in (select ");
                whereClause.append(Im.Contacts._ID);
                whereClause.append(" from ");
                whereClause.append(TABLE_CONTACTS);
                whereClause.append(" where ");
                whereClause.append(Im.Contacts.ACCOUNT);
                whereClause.append("='");
                whereClause.append(url.getLastPathSegment());
                whereClause.append("')");

                if (DBG) log("deleteInternal (MATCH_CHATS_BY_ACCOUNT): sel => " +
                        whereClause.toString());

                changedItemId = null;
                break;

            case MATCH_CHATS_ID:
                tableToChange = TABLE_CHATS;
                changedItemId = url.getPathSegments().get(1);
                idColumnName = Im.Chats.CONTACT_ID;
                break;

            case MATCH_PRESENCE:
                tableToChange = TABLE_PRESENCE;
                break;

            case MATCH_PRESENCE_ID:
                tableToChange = TABLE_PRESENCE;
                changedItemId = url.getPathSegments().get(1);
                idColumnName = Im.Presence.CONTACT_ID;
                break;

            case MATCH_PRESENCE_BY_ACCOUNT:
                tableToChange = TABLE_PRESENCE;

                if (whereClause.length() > 0) {
                    whereClause.append(" AND ");
                }
                whereClause.append(Im.Presence.CONTACT_ID);
                whereClause.append(" in (select ");
                whereClause.append(Im.Contacts._ID);
                whereClause.append(" from ");
                whereClause.append(TABLE_CONTACTS);
                whereClause.append(" where ");
                whereClause.append(Im.Contacts.ACCOUNT);
                whereClause.append("='");
                whereClause.append(url.getLastPathSegment());
                whereClause.append("')");

                if (DBG) log("deleteInternal (MATCH_PRESENCE_BY_ACCOUNT): sel => " +
                        whereClause.toString());

                changedItemId = null;
                break;

            case MATCH_SESSIONS:
                tableToChange = TABLE_SESSION_COOKIES;
                break;

            case MATCH_SESSIONS_BY_PROVIDER:
                tableToChange = TABLE_SESSION_COOKIES;
                changedItemId = url.getPathSegments().get(2);
                idColumnName = Im.SessionCookies.ACCOUNT;
                break;

            case MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME:
                tableToChange = TABLE_PROVIDER_SETTINGS;

                String providerId = url.getPathSegments().get(1);
                String name = url.getPathSegments().get(2);

                appendWhere(whereClause, Im.ProviderSettings.PROVIDER, "=", providerId);
                appendWhere(whereClause, Im.ProviderSettings.NAME, "=", name);
                break;

            case MATCH_OUTGOING_RMQ_MESSAGES:
                tableToChange = TABLE_OUTGOING_RMQ_MESSAGES;
                break;

            case MATCH_LAST_RMQ_ID:
                tableToChange = TABLE_LAST_RMQ_ID;
                break;

            case MATCH_BRANDING_RESOURCE_MAP_CACHE:
                tableToChange = TABLE_BRANDING_RESOURCE_MAP_CACHE;
                break;

            default:
                throw new UnsupportedOperationException("Cannot delete that URL: " + url);
        }

        if (idColumnName == null) {
            idColumnName = "_id";
        }

        if (changedItemId != null) {
            appendWhere(whereClause, idColumnName, "=", changedItemId);
        }

        if (DBG) log("delete from " + url + " WHERE  " + whereClause);

        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        int count = db.delete(tableToChange, whereClause.toString(), whereArgs);

        if (contactDeleted && count > 0) {
            // since the contact cleanup triggers no longer work for cross database tables,
            // we have to do it by hand here.
            performContactRemovalCleanup(deletedContactId);
        }

        if (count > 0) {
            // In most case, we query contacts with presence and chats joined, thus
            // we should also notify that contacts changes when presence or chats changed.
            if (match == MATCH_CHATS || match == MATCH_CHATS_ID
                    || match == MATCH_PRESENCE || match == MATCH_PRESENCE_ID
                    || match == MATCH_CONTACTS_BAREBONE) {
                getContext().getContentResolver().notifyChange(Im.Contacts.CONTENT_URI, null);
            } else if (notifyMessagesContentUri) {
                getContext().getContentResolver().notifyChange(Im.Messages.CONTENT_URI, null);
            } else if (notifyGroupMessagesContentUri) {
                getContext().getContentResolver().notifyChange(Im.GroupMessages.CONTENT_URI, null);
            } else if (notifyContactListContentUri) {
                getContext().getContentResolver().notifyChange(Im.ContactList.CONTENT_URI, null);
            } else if (notifyProviderAccountContentUri) {
                if (DBG) log("notify delete for " + Im.Provider.CONTENT_URI_WITH_ACCOUNT);
                getContext().getContentResolver().notifyChange(Im.Provider.CONTENT_URI_WITH_ACCOUNT,
                        null);
            }
            
            if (backfillQuickSwitchSlots) {
                backfillQuickSwitchSlots();
            }
        }

        return count;
    
private voiddeleteWithContactId(android.database.sqlite.SQLiteDatabase db, long contactId, java.lang.String tableName, java.lang.String columnName)

        db.delete(tableName, columnName + '=" + contactId, null /* selection args */);
    
private intfindEmptyQuickSwitchSlot()

        Cursor c = queryInternal(Im.Chats.CONTENT_URI, FIND_SHORTCUT_PROJECTION, null, null, null);
        final int N = c.getCount();

        try {
            //  If there are 10 or more chats then all the quick switch slots are already filled
            if (N >= 10) {
                return -1;
            }

            int slots = 0;
            int column = c.getColumnIndex(Im.Chats.SHORTCUT);
            
            //  The map is here because numbers go from 0-9, but we want to assign slots in
            //  0, 9, 8, ..., 1 order to match the right-to-left reading of the number row
            //  on the keyboard.
            int[] map = new int[] { 0, 9, 8, 7, 6, 5, 4, 3, 2, 1 };

            //  Mark all the slots that are in use
            //  The shortcuts represent actual keyboard number row keys, and not ordinals.
            //  So 7 would mean the shortcut is the 7 key on the keyboard and NOT the 7th
            //  shortcut.  The passing of slot through map[] below maps these keyboard key
            //  shortcuts into an ordinal bit position in the 'slots' bitfield.
            for (c.moveToFirst(); ! c.isAfterLast(); c.moveToNext()) {
                int slot = c.getInt(column);
                
                if (slot != -1) {
                    slots |= (1 << map[slot]);
                }
            }

            //  Try to find an empty one
            //  As we exit this, the push of i through map[] maps the ordinal bit position
            //  in the 'slots' bitfield onto a key on the number row of the device keyboard.
            //  The keyboard key is what is used to designate the shortcut.
            for (int i = 0; i < 10; i++) {
                if ((slots & (1 << i)) == 0) {
                    return map[i];
                }
            }
            
            return -1;
        } finally {
            c.close();
        }
    
public java.lang.StringgetType(android.net.Uri url)

        int match = mUrlMatcher.match(url);
        switch (match) {
            case MATCH_PROVIDERS:
                return Im.Provider.CONTENT_TYPE;

            case MATCH_PROVIDERS_BY_ID:
                return Im.Provider.CONTENT_ITEM_TYPE;
            
            case MATCH_ACCOUNTS:
                return Im.Account.CONTENT_TYPE;

            case MATCH_ACCOUNTS_BY_ID:
                return Im.Account.CONTENT_ITEM_TYPE;

            case MATCH_CONTACTS:
            case MATCH_CONTACTS_BY_PROVIDER:
            case MATCH_ONLINE_CONTACTS_BY_PROVIDER:
            case MATCH_OFFLINE_CONTACTS_BY_PROVIDER:
            case MATCH_CONTACTS_BULK:
            case MATCH_CONTACTS_BAREBONE:
            case MATCH_CONTACTS_JOIN_PRESENCE:
                return Im.Contacts.CONTENT_TYPE;

            case MATCH_CONTACT:
                return Im.Contacts.CONTENT_ITEM_TYPE;

            case MATCH_CONTACTLISTS:
            case MATCH_CONTACTLISTS_BY_PROVIDER:
                return Im.ContactList.CONTENT_TYPE;

            case MATCH_CONTACTLIST:
                return Im.ContactList.CONTENT_ITEM_TYPE;

            case MATCH_BLOCKEDLIST:
            case MATCH_BLOCKEDLIST_BY_PROVIDER:
                return Im.BlockedList.CONTENT_TYPE;

            case MATCH_CONTACTS_ETAGS:
            case MATCH_CONTACTS_ETAG:
                return Im.ContactsEtag.CONTENT_TYPE;

            case MATCH_MESSAGES:
            case MATCH_MESSAGES_BY_CONTACT:
                return Im.Messages.CONTENT_TYPE;

            case MATCH_MESSAGE:
                return Im.Messages.CONTENT_ITEM_TYPE;

            case MATCH_GROUP_MESSAGES:
            case MATCH_GROUP_MESSAGE_BY:
                return Im.GroupMessages.CONTENT_TYPE;

            case MATCH_GROUP_MESSAGE:
                return Im.GroupMessages.CONTENT_ITEM_TYPE;

            case MATCH_PRESENCE:
            case MATCH_PRESENCE_BULK:
                return Im.Presence.CONTENT_TYPE;

            case MATCH_AVATARS:
                return Im.Avatars.CONTENT_TYPE;

            case MATCH_AVATAR:
                return Im.Avatars.CONTENT_ITEM_TYPE;

            case MATCH_CHATS:
                return Im.Chats.CONTENT_TYPE;

            case MATCH_CHATS_ID:
                return Im.Chats.CONTENT_ITEM_TYPE;

            case MATCH_INVITATIONS:
                return Im.Invitation.CONTENT_TYPE;

            case MATCH_INVITATION:
                return Im.Invitation.CONTENT_ITEM_TYPE;

            case MATCH_GROUP_MEMBERS:
            case MATCH_GROUP_MEMBERS_BY_GROUP:
                return Im.GroupMembers.CONTENT_TYPE;

            case MATCH_SESSIONS:
            case MATCH_SESSIONS_BY_PROVIDER:
                return Im.SessionCookies.CONTENT_TYPE;

            case MATCH_PROVIDER_SETTINGS:
                return Im.ProviderSettings.CONTENT_TYPE;

            case MATCH_ACCOUNTS_STATUS:
                return Im.AccountStatus.CONTENT_TYPE;

            case MATCH_ACCOUNT_STATUS:
                return Im.AccountStatus.CONTENT_ITEM_TYPE;

            default:
                throw new IllegalArgumentException("Unknown URL");
        }
    
public final android.net.Uriinsert(android.net.Uri url, android.content.ContentValues values)

        Uri result;
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        db.beginTransaction();
        try {
            result = insertInternal(url, values);
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
        if (result != null) {
            getContext().getContentResolver()
                    .notifyChange(url, null /* observer */, false /* sync */);
        }
        return result;
    
booleaninsertBulkContacts(android.content.ContentValues values)

        //if (DBG) log("insertBulkContacts: begin");
        
        ArrayList<String> usernames = values.getStringArrayList(Im.Contacts.USERNAME);
        ArrayList<String> nicknames = values.getStringArrayList(Im.Contacts.NICKNAME);
        int usernameCount = usernames.size();
        int nicknameCount = nicknames.size();

        if (usernameCount != nicknameCount) {
            Log.e(LOG_TAG, "[ImProvider] insertBulkContacts: input bundle " +
                    "username & nickname lists have diff. length!");
            return false;
        }

        ArrayList<String> contactTypeArray = values.getStringArrayList(Im.Contacts.TYPE);
        ArrayList<String> subscriptionStatusArray =
                values.getStringArrayList(Im.Contacts.SUBSCRIPTION_STATUS);
        ArrayList<String> subscriptionTypeArray =
                values.getStringArrayList(Im.Contacts.SUBSCRIPTION_TYPE);
        ArrayList<String> quickContactArray = values.getStringArrayList(Im.Contacts.QUICK_CONTACT);
        ArrayList<String> rejectedArray = values.getStringArrayList(Im.Contacts.REJECTED);
        int sum = 0;
            
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();

        db.beginTransaction();
        try {
            Long provider = values.getAsLong(Im.Contacts.PROVIDER);
            Long account = values.getAsLong(Im.Contacts.ACCOUNT);
            Long listId = values.getAsLong(Im.Contacts.CONTACTLIST);

            ContentValues contactValues = new ContentValues();
            contactValues.put(Im.Contacts.PROVIDER, provider);
            contactValues.put(Im.Contacts.ACCOUNT, account);
            contactValues.put(Im.Contacts.CONTACTLIST, listId);
            ContentValues presenceValues = new ContentValues();
            presenceValues.put(Im.Presence.PRESENCE_STATUS,
                    Im.Presence.OFFLINE);

            for (int i=0; i<usernameCount; i++) {
                String username = usernames.get(i);
                String nickname = nicknames.get(i);
                int type = 0;
                int subscriptionStatus = 0;
                int subscriptionType = 0;
                int quickContact = 0;
                int rejected = 0;

                try {
                    type = Integer.parseInt(contactTypeArray.get(i));
                    if (subscriptionStatusArray != null) {
                        subscriptionStatus = Integer.parseInt(subscriptionStatusArray.get(i));
                    }
                    if (subscriptionTypeArray != null) {
                        subscriptionType = Integer.parseInt(subscriptionTypeArray.get(i));
                    }
                    if (quickContactArray != null) {
                        quickContact = Integer.parseInt(quickContactArray.get(i));
                    }
                    if (rejectedArray != null) {
                        rejected = Integer.parseInt(rejectedArray.get(i));
                    }
                } catch (NumberFormatException ex) {
                    Log.e(LOG_TAG, "insertBulkContacts: caught " + ex);
                }

                /*
                if (DBG) log("insertBulkContacts[" + i + "] username=" +
                        username + ", nickname=" + nickname + ", type=" + type +
                        ", subscriptionStatus=" + subscriptionStatus + ", subscriptionType=" +
                        subscriptionType + ", qc=" + quickContact);
                */

                contactValues.put(Im.Contacts.USERNAME, username);
                contactValues.put(Im.Contacts.NICKNAME, nickname);
                contactValues.put(Im.Contacts.TYPE, type);
                if (subscriptionStatusArray != null) {
                    contactValues.put(Im.Contacts.SUBSCRIPTION_STATUS, subscriptionStatus);
                }
                if (subscriptionTypeArray != null) {
                    contactValues.put(Im.Contacts.SUBSCRIPTION_TYPE, subscriptionType);
                }
                if (quickContactArray != null) {
                    contactValues.put(Im.Contacts.QUICK_CONTACT, quickContact);
                }
                if (rejectedArray != null) {
                    contactValues.put(Im.Contacts.REJECTED, rejected);
                }

                long rowId = 0;

                /* save this code for when we add constraint (account, username) to the contacts
                   table
                try {
                    rowId = db.insertOrThrow(TABLE_CONTACTS, USERNAME, contactValues);
                } catch (android.database.sqlite.SQLiteConstraintException ex) {
                    if (DBG) log("insertBulkContacts: insert " + username + " caught " + ex);
                    
                    // append username to the selection clause
                    updateSelection.delete(0, updateSelection.length());
                    updateSelection.append(Im.Contacts.USERNAME);
                    updateSelection.append("=?");
                    updateSelectionArgs[0] = username;

                    int updated = db.update(TABLE_CONTACTS, contactValues,
                            updateSelection.toString(), updateSelectionArgs);

                    if (DBG && updated != 1) {
                        log("insertBulkContacts: update " + username + " failed!");
                    }
                }
                */

                rowId = db.insert(TABLE_CONTACTS, USERNAME, contactValues);
                if (rowId > 0) {
                    sum++;
                    if (!USE_CONTACT_PRESENCE_TRIGGER) {
                        // seed the presence for the new contact
                        //if (DBG) log("seedPresence for pid " + rowId);
                        presenceValues.put(Im.Presence.CONTACT_ID, rowId);
                        db.insert(TABLE_PRESENCE, null, presenceValues);
                    }
                }
                
                // yield the lock if anyone else is trying to
                // perform a db operation here.
                db.yieldIfContended();
            }

            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }

        // We know that we succeeded becuase endTransaction throws if the transaction failed.
        if (DBG) log("insertBulkContacts: added " + sum + " contacts!");
        return true;
    
public android.net.UriinsertInternal(android.net.Uri url, android.content.ContentValues initialValues)

        Uri resultUri = null;
        long rowID = 0;
        boolean notifyContactListContentUri = false;
        boolean notifyContactContentUri = false;
        boolean notifyMessagesContentUri = false;
        boolean notifyGroupMessagesContentUri = false;
        boolean notifyProviderAccountContentUri = false;

        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        int match = mUrlMatcher.match(url);

        if (DBG) log("insert to " + url + ", match " + match);
        switch (match) {
            case MATCH_PROVIDERS:
                // Insert into the providers table
                rowID = db.insert(TABLE_PROVIDERS, "name", initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.Provider.CONTENT_URI + "/" + rowID);
                }
                notifyProviderAccountContentUri = true;
                break;

            case MATCH_ACCOUNTS:
                // Insert into the accounts table
                rowID = db.insert(TABLE_ACCOUNTS, "name", initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.Account.CONTENT_URI + "/" + rowID);
                }
                notifyProviderAccountContentUri = true;
                break;

            case MATCH_CONTACTS_BY_PROVIDER:
                appendValuesFromUrl(initialValues, url, Im.Contacts.PROVIDER,
                    Im.Contacts.ACCOUNT);
                // fall through
            case MATCH_CONTACTS:
            case MATCH_CONTACTS_BAREBONE:
                // Insert into the contacts table
                rowID = db.insert(TABLE_CONTACTS, "username", initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.Contacts.CONTENT_URI + "/" + rowID);
                }

                notifyContactContentUri = true;
                break;

            case MATCH_CONTACTS_BULK:
                if (insertBulkContacts(initialValues)) {
                    // notify change using the "content://im/contacts" url,
                    // so the change will be observed by listeners interested
                    // in contacts changes.
                    resultUri = Im.Contacts.CONTENT_URI;
                }
                notifyContactContentUri = true;
                break;

            case MATCH_CONTACTLISTS_BY_PROVIDER:
                appendValuesFromUrl(initialValues, url, Im.ContactList.PROVIDER,
                        Im.ContactList.ACCOUNT);
                // fall through
            case MATCH_CONTACTLISTS:
                // Insert into the contactList table
                rowID = db.insert(TABLE_CONTACT_LIST, "name", initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.ContactList.CONTENT_URI + "/" + rowID);
                }
                notifyContactListContentUri = true;
                break;

            case MATCH_BLOCKEDLIST_BY_PROVIDER:
                appendValuesFromUrl(initialValues, url, Im.BlockedList.PROVIDER,
                    Im.BlockedList.ACCOUNT);
                // fall through
            case MATCH_BLOCKEDLIST:
                // Insert into the blockedList table
                rowID = db.insert(TABLE_BLOCKED_LIST, "username", initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.BlockedList.CONTENT_URI + "/" + rowID);
                }

                break;

            case MATCH_CONTACTS_ETAGS:
                rowID = db.replace(TABLE_CONTACTS_ETAG, Im.ContactsEtag.ETAG, initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.ContactsEtag.CONTENT_URI + "/" + rowID);
                }
                break;

            case MATCH_MESSAGES_BY_CONTACT:
                appendValuesFromUrl(initialValues, url, Im.Messages.PROVIDER,
                    Im.Messages.ACCOUNT, Im.Messages.CONTACT);
                notifyMessagesContentUri = true;
                // fall through
            case MATCH_MESSAGES:
                // Insert into the messages table.
                rowID = db.insert(TABLE_MESSAGES, "contact", initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.Messages.CONTENT_URI + "/" + rowID);
                }

                break;

            case MATCH_INVITATIONS:
                rowID = db.insert(TABLE_INVITATIONS, null, initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.Invitation.CONTENT_URI + "/" + rowID);
                }
                break;

            case MATCH_GROUP_MEMBERS:
                rowID = db.insert(TABLE_GROUP_MEMBERS, "nickname", initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.GroupMembers.CONTENT_URI + "/" + rowID);
                }
                break;

            case MATCH_GROUP_MEMBERS_BY_GROUP:
                appendValuesFromUrl(initialValues, url, Im.GroupMembers.GROUP);
                rowID = db.insert(TABLE_GROUP_MEMBERS, "nickname", initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.GroupMembers.CONTENT_URI + "/" + rowID);
                }
                break;

            case MATCH_GROUP_MESSAGE_BY:
                appendValuesFromUrl(initialValues, url, Im.GroupMembers.GROUP);
                notifyGroupMessagesContentUri = true;
                // fall through
            case MATCH_GROUP_MESSAGES:
                rowID = db.insert(TABLE_GROUP_MESSAGES, "group", initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.GroupMessages.CONTENT_URI + "/" + rowID);
                }
                break;

            case MATCH_AVATAR_BY_PROVIDER:
                appendValuesFromUrl(initialValues, url, Im.Avatars.PROVIDER, Im.Avatars.ACCOUNT);
                // fall through
            case MATCH_AVATARS:
                // Insert into the avatars table
                rowID = db.replace(TABLE_AVATARS, "contact", initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.Avatars.CONTENT_URI + "/" + rowID);
                }
                break;

            case MATCH_CHATS_ID:
                appendValuesFromUrl(initialValues, url, Im.Chats.CONTACT_ID);
                // fall through
            case MATCH_CHATS:
                // Insert into the chats table
                initialValues.put(Im.Chats.SHORTCUT, -1);
                rowID = db.replace(TABLE_CHATS, Im.Chats.CONTACT_ID, initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.Chats.CONTENT_URI + "/" + rowID);
                    addToQuickSwitch(rowID);
                }
                notifyContactContentUri = true;
                break;

            case MATCH_PRESENCE:
                rowID = db.replace(TABLE_PRESENCE, null, initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.Presence.CONTENT_URI + "/" + rowID);
                }
                notifyContactContentUri = true;
                break;

            case MATCH_PRESENCE_SEED_BY_ACCOUNT:
                try {
                    seedInitialPresenceByAccount(Long.parseLong(url.getLastPathSegment()));
                    resultUri = Im.Presence.CONTENT_URI;
                } catch (NumberFormatException ex) {
                    throw new IllegalArgumentException();
                }
                break;

            case MATCH_SESSIONS_BY_PROVIDER:
                appendValuesFromUrl(initialValues, url, Im.SessionCookies.PROVIDER,
                        Im.SessionCookies.ACCOUNT);
                // fall through
            case MATCH_SESSIONS:
                rowID = db.insert(TABLE_SESSION_COOKIES, null, initialValues);
                if(rowID > 0) {
                    resultUri = Uri.parse(Im.SessionCookies.CONTENT_URI + "/" + rowID);
                }
                break;

            case MATCH_PROVIDER_SETTINGS:
                rowID = db.replace(TABLE_PROVIDER_SETTINGS, null, initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.ProviderSettings.CONTENT_URI + "/" + rowID);
                }
                break;

            case MATCH_OUTGOING_RMQ_MESSAGES:
                rowID = db.insert(TABLE_OUTGOING_RMQ_MESSAGES, null, initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.OutgoingRmq.CONTENT_URI + "/" + rowID);
                }
                break;

            case MATCH_LAST_RMQ_ID:
                rowID = db.replace(TABLE_LAST_RMQ_ID, null, initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.LastRmqId.CONTENT_URI + "/" + rowID);
                }
                break;

            case MATCH_ACCOUNTS_STATUS:
                rowID = db.replace(TABLE_ACCOUNT_STATUS, null, initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.AccountStatus.CONTENT_URI + "/" + rowID);
                }
                notifyProviderAccountContentUri = true;
                break;

            case MATCH_BRANDING_RESOURCE_MAP_CACHE:
                rowID = db.insert(TABLE_BRANDING_RESOURCE_MAP_CACHE, null, initialValues);
                if (rowID > 0) {
                    resultUri = Uri.parse(Im.BrandingResourceMapCache.CONTENT_URI + "/" + rowID);
                }
                break;

            default:
                throw new UnsupportedOperationException("Cannot insert into URL: " + url);
        }
        // TODO: notify the data change observer?

        if (resultUri != null) {
            ContentResolver resolver = getContext().getContentResolver();

            // In most case, we query contacts with presence and chats joined, thus
            // we should also notify that contacts changes when presence or chats changed.
            if (notifyContactContentUri) {
                resolver.notifyChange(Im.Contacts.CONTENT_URI, null);
            }

            if (notifyContactListContentUri) {
                resolver.notifyChange(Im.ContactList.CONTENT_URI, null);
            }

            if (notifyMessagesContentUri) {
                resolver.notifyChange(Im.Messages.CONTENT_URI, null);
            }

            if (notifyGroupMessagesContentUri) {
                resolver.notifyChange(Im.GroupMessages.CONTENT_URI, null);
            }

            if (notifyProviderAccountContentUri) {
                if (DBG) log("notify insert for " + Im.Provider.CONTENT_URI_WITH_ACCOUNT);
                resolver.notifyChange(Im.Provider.CONTENT_URI_WITH_ACCOUNT,
                        null);
            }
        }
        return resultUri;
    
static voidlog(java.lang.String message)

        Log.d(LOG_TAG, message);
    
public booleanonCreate()

        mOpenHelper = new DatabaseHelper(getContext());
        return true;
    
public android.os.ParcelFileDescriptoropenFile(android.net.Uri uri, java.lang.String mode)

        return openFileHelper(uri, mode);
    
private voidperformComplexDelete(android.database.sqlite.SQLiteDatabase db, java.lang.String tableName, java.lang.String selection, java.lang.String[] selectionArgs)

        if (DBG) log("performComplexDelete for table " + tableName + ", selection => " + selection);
        int count = db.delete(tableName, selection, selectionArgs);
        if (DBG) log("performComplexDelete: deleted " + count + " rows");
    
private voidperformContactRemovalCleanup(long contactId)


        
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();

        if (contactId > 0) {
            deleteWithContactId(db, contactId, TABLE_PRESENCE, Im.Presence.CONTACT_ID);
            deleteWithContactId(db, contactId, TABLE_CHATS, Im.Chats.CONTACT_ID);
            deleteWithContactId(db, contactId, TABLE_GROUP_MEMBERS, Im.GroupMembers.GROUP);
            deleteWithContactId(db, contactId, TABLE_GROUP_MESSAGES, Im.GroupMessages.GROUP);
        } else {
            performComplexDelete(db, TABLE_PRESENCE, DELETE_PRESENCE_SELECTION, null);
            performComplexDelete(db, TABLE_CHATS, DELETE_CHATS_SELECTION, null);
            performComplexDelete(db, TABLE_GROUP_MEMBERS, DELETE_GROUP_MEMBER_SELECTION, null);
            performComplexDelete(db, TABLE_GROUP_MESSAGES, DELETE_GROUP_MESSAGES_SELECTION, null);
        }
    
public final android.database.Cursorquery(android.net.Uri url, java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder)

        return queryInternal(url, projection, selection, selectionArgs, sortOrder);
    
public android.database.CursorqueryInternal(android.net.Uri url, java.lang.String[] projectionIn, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sort)

        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        StringBuilder whereClause = new StringBuilder();
        if(selection != null) {
            whereClause.append(selection);
        }
        String groupBy = null;
        String limit = null;

        // Generate the body of the query
        int match = mUrlMatcher.match(url);

        if (DBG) {
            log("query " + url + ", match " + match + ", where " + selection);
            if (selectionArgs != null) {
                for (String selectionArg : selectionArgs) {
                    log("     selectionArg: " + selectionArg);
                }
            }
        }

        switch (match) {
            case MATCH_PROVIDERS_BY_ID:
                appendWhere(whereClause, Im.Provider._ID, "=", url.getPathSegments().get(1));
                // fall thru.

            case MATCH_PROVIDERS:
                qb.setTables(TABLE_PROVIDERS);
                break;

            case MATCH_PROVIDERS_WITH_ACCOUNT:
                qb.setTables(PROVIDER_JOIN_ACCOUNT_TABLE);
                qb.setProjectionMap(sProviderAccountsProjectionMap);
                break;

            case MATCH_ACCOUNTS_BY_ID:
                appendWhere(whereClause, Im.Account._ID, "=", url.getPathSegments().get(1));
                // falls down
            case MATCH_ACCOUNTS:
                qb.setTables(TABLE_ACCOUNTS);
                break;

            case MATCH_CONTACTS:
                qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE);
                qb.setProjectionMap(sContactsProjectionMap);
                break;

            case MATCH_CONTACTS_JOIN_PRESENCE:
                qb.setTables(CONTACT_JOIN_PRESENCE_TABLE);
                qb.setProjectionMap(sContactsProjectionMap);
                break;

            case MATCH_CONTACTS_BAREBONE:
                qb.setTables(TABLE_CONTACTS);
                break;

            case MATCH_CHATTING_CONTACTS:
                qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE);
                qb.setProjectionMap(sContactsProjectionMap);
                appendWhere(whereClause, "chats.last_message_date IS NOT NULL");
                // no need to add the non blocked contacts clause because
                // blocked contacts can't have conversations.
                break;

            case MATCH_CONTACTS_BY_PROVIDER:
                buildQueryContactsByProvider(qb, whereClause, url);
                appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE);
                break;

            case MATCH_CHATTING_CONTACTS_BY_PROVIDER:
                buildQueryContactsByProvider(qb, whereClause, url);
                appendWhere(whereClause, "chats.last_message_date IS NOT NULL");
                // no need to add the non blocked contacts clause because
                // blocked contacts can't have conversations.
                break;

            case MATCH_NO_CHATTING_CONTACTS_BY_PROVIDER:
                buildQueryContactsByProvider(qb, whereClause, url);
                appendWhere(whereClause, "chats.last_message_date IS NULL");
                appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE);
                break;

            case MATCH_ONLINE_CONTACTS_BY_PROVIDER:
                buildQueryContactsByProvider(qb, whereClause, url);
                appendWhere(whereClause, Im.Contacts.PRESENCE_STATUS, "!=", Im.Presence.OFFLINE);
                appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE);
                break;

            case MATCH_OFFLINE_CONTACTS_BY_PROVIDER:
                buildQueryContactsByProvider(qb, whereClause, url);
                appendWhere(whereClause, Im.Contacts.PRESENCE_STATUS, "=", Im.Presence.OFFLINE);
                appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE);
                break;

            case MATCH_BLOCKED_CONTACTS:
                qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE);
                qb.setProjectionMap(sContactsProjectionMap);
                appendWhere(whereClause, BLOCKED_CONTACTS_WHERE_CLAUSE);
                break;

            case MATCH_CONTACT:
                qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE);
                qb.setProjectionMap(sContactsProjectionMap);
                appendWhere(whereClause, "contacts._id", "=", url.getPathSegments().get(1));
                break;

            case MATCH_ONLINE_CONTACT_COUNT:
                qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_TABLE);
                qb.setProjectionMap(sContactsProjectionMap);
                appendWhere(whereClause, Im.Contacts.PRESENCE_STATUS, "!=", Im.Presence.OFFLINE);
                appendWhere(whereClause, "chats.last_message_date IS NULL");
                appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE);
                groupBy = Im.Contacts.CONTACTLIST;
                break;

            case MATCH_CONTACTLISTS_BY_PROVIDER:
                appendWhere(whereClause, Im.ContactList.ACCOUNT, "=",
                        url.getPathSegments().get(2));
                // fall through
            case MATCH_CONTACTLISTS:
                qb.setTables(TABLE_CONTACT_LIST);
                qb.setProjectionMap(sContactListProjectionMap);
                break;

            case MATCH_CONTACTLIST:
                qb.setTables(TABLE_CONTACT_LIST);
                appendWhere(whereClause, Im.ContactList._ID, "=", url.getPathSegments().get(1));
                break;

            case MATCH_BLOCKEDLIST:
                qb.setTables(BLOCKEDLIST_JOIN_AVATAR_TABLE);
                qb.setProjectionMap(sBlockedListProjectionMap);
                break;

            case MATCH_BLOCKEDLIST_BY_PROVIDER:
                qb.setTables(BLOCKEDLIST_JOIN_AVATAR_TABLE);
                qb.setProjectionMap(sBlockedListProjectionMap);
                appendWhere(whereClause, Im.BlockedList.ACCOUNT, "=",
                        url.getPathSegments().get(2));
                break;

            case MATCH_CONTACTS_ETAGS:
                qb.setTables(TABLE_CONTACTS_ETAG);
                break;

            case MATCH_CONTACTS_ETAG:
                qb.setTables(TABLE_CONTACTS_ETAG);
                appendWhere(whereClause, "_id", "=", url.getPathSegments().get(1));
                break;

            case MATCH_MESSAGES:
                qb.setTables(TABLE_MESSAGES);
                break;

            case MATCH_MESSAGES_BY_CONTACT:
                // we don't really need the provider id in query. account id
                // is enough.
                qb.setTables(TABLE_MESSAGES);
                appendWhere(whereClause, Im.Messages.ACCOUNT, "=",
                        url.getPathSegments().get(2));
                appendWhere(whereClause, Im.Messages.CONTACT, "=",
                    decodeURLSegment(url.getPathSegments().get(3)));
                break;

            case MATCH_MESSAGE:
                qb.setTables(TABLE_MESSAGES);
                appendWhere(whereClause, Im.Messages._ID, "=", url.getPathSegments().get(1));
                break;

            case MATCH_INVITATIONS:
                qb.setTables(TABLE_INVITATIONS);
                break;

            case MATCH_INVITATION:
                qb.setTables(TABLE_INVITATIONS);
                appendWhere(whereClause, Im.Invitation._ID, "=", url.getPathSegments().get(1));
                break;

            case MATCH_GROUP_MEMBERS:
                qb.setTables(TABLE_GROUP_MEMBERS);
                break;

            case MATCH_GROUP_MEMBERS_BY_GROUP:
                qb.setTables(TABLE_GROUP_MEMBERS);
                appendWhere(whereClause, Im.GroupMembers.GROUP, "=", url.getPathSegments().get(1));
                break;

            case MATCH_GROUP_MESSAGES:
                qb.setTables(TABLE_GROUP_MESSAGES);
                break;

            case MATCH_GROUP_MESSAGE_BY:
                qb.setTables(TABLE_GROUP_MESSAGES);
                appendWhere(whereClause, Im.GroupMessages.GROUP, "=",
                        url.getPathSegments().get(1));
                break;

            case MATCH_GROUP_MESSAGE:
                qb.setTables(TABLE_GROUP_MESSAGES);
                appendWhere(whereClause, Im.GroupMessages._ID, "=",
                        url.getPathSegments().get(1));
                break;

            case MATCH_AVATARS:
                qb.setTables(TABLE_AVATARS);
                break;

            case MATCH_AVATAR_BY_PROVIDER:
                qb.setTables(TABLE_AVATARS);
                appendWhere(whereClause, Im.Avatars.ACCOUNT, "=", url.getPathSegments().get(2));
                break;

            case MATCH_CHATS:
                qb.setTables(TABLE_CHATS);
                break;

            case MATCH_CHATS_ID:
                qb.setTables(TABLE_CHATS);
                appendWhere(whereClause, Im.Chats.CONTACT_ID, "=", url.getPathSegments().get(1));
                break;

            case MATCH_PRESENCE:
                qb.setTables(TABLE_PRESENCE);
                break;

            case MATCH_PRESENCE_ID:
                qb.setTables(TABLE_PRESENCE);
                appendWhere(whereClause, Im.Presence.CONTACT_ID, "=", url.getPathSegments().get(1));
                break;

            case MATCH_SESSIONS:
                qb.setTables(TABLE_SESSION_COOKIES);
                break;

            case MATCH_SESSIONS_BY_PROVIDER:
                qb.setTables(TABLE_SESSION_COOKIES);
                appendWhere(whereClause, Im.SessionCookies.ACCOUNT, "=", url.getPathSegments().get(2));
                break;

            case MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME:
                appendWhere(whereClause, Im.ProviderSettings.NAME, "=", url.getPathSegments().get(2));
                // fall through
            case MATCH_PROVIDER_SETTINGS_BY_ID:
                appendWhere(whereClause, Im.ProviderSettings.PROVIDER, "=", url.getPathSegments().get(1));
                // fall through
            case MATCH_PROVIDER_SETTINGS:
                qb.setTables(TABLE_PROVIDER_SETTINGS);
                break;

            case MATCH_OUTGOING_RMQ_MESSAGES:
                qb.setTables(TABLE_OUTGOING_RMQ_MESSAGES);
                break;

            case MATCH_OUTGOING_HIGHEST_RMQ_ID:
                qb.setTables(TABLE_OUTGOING_RMQ_MESSAGES);
                sort = "rmq_id DESC";
                limit = "1";
                break;

            case MATCH_LAST_RMQ_ID:
                qb.setTables(TABLE_LAST_RMQ_ID);
                limit = "1";
                break;

            case MATCH_ACCOUNTS_STATUS:
                qb.setTables(TABLE_ACCOUNT_STATUS);
                break;

            case MATCH_ACCOUNT_STATUS:
                qb.setTables(TABLE_ACCOUNT_STATUS);
                appendWhere(whereClause, Im.AccountStatus.ACCOUNT, "=",
                        url.getPathSegments().get(1));
                break;

            case MATCH_BRANDING_RESOURCE_MAP_CACHE:
                qb.setTables(TABLE_BRANDING_RESOURCE_MAP_CACHE);
                break;

            default:
                throw new IllegalArgumentException("Unknown URL " + url);
        }

        // run the query
        final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
        Cursor c = null;

        try {
            c = qb.query(db, projectionIn, whereClause.toString(), selectionArgs,
                    groupBy, null, sort, limit);
            if (c != null) {
                switch(match) {
                case MATCH_CHATTING_CONTACTS:
                case MATCH_CONTACTS_BY_PROVIDER:
                case MATCH_CHATTING_CONTACTS_BY_PROVIDER:
                case MATCH_ONLINE_CONTACTS_BY_PROVIDER:
                case MATCH_OFFLINE_CONTACTS_BY_PROVIDER:
                case MATCH_CONTACTS_BAREBONE:
                case MATCH_CONTACTS_JOIN_PRESENCE:
                case MATCH_ONLINE_CONTACT_COUNT:
                    url = Im.Contacts.CONTENT_URI;
                    break;
                }
                if (DBG) log("set notify url " + url);
                c.setNotificationUri(getContext().getContentResolver(), url);
            }
        } catch (Exception ex) {
            Log.e(LOG_TAG, "query db caught ", ex);
        }

        return c;
    
private voidseedInitialPresenceByAccount(long account)
This method first performs a query for all the contacts (for the given account) that don't have a presence entry in the presence table. Then for each of those contacts, the method creates a presence row. The whole thing is done inside one database transaction to increase performance.

param
account the account of the contacts for which we want to create seed presence rows.


                                                                         
        
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        qb.setTables(TABLE_CONTACTS);
        qb.setProjectionMap(sContactsProjectionMap);

        mQueryContactPresenceSelectionArgs[0] = String.valueOf(account);

        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        db.beginTransaction();

        Cursor c = null;

        try {
            ContentValues presenceValues = new ContentValues();
            presenceValues.put(Im.Presence.PRESENCE_STATUS, Im.Presence.OFFLINE);
            presenceValues.put(Im.Presence.PRESENCE_CUSTOM_STATUS, "");

            // First: update all the presence for the account so they are offline
            StringBuilder buf = new StringBuilder();
            buf.append(Im.Presence.CONTACT_ID);
            buf.append(" in (select ");
            buf.append(Im.Contacts._ID);
            buf.append(" from ");
            buf.append(TABLE_CONTACTS);
            buf.append(" where ");
            buf.append(Im.Contacts.ACCOUNT);
            buf.append("=?) ");

            String selection = buf.toString();
            if (DBG) log("seedInitialPresence: reset presence selection=" + selection);

            int count = db.update(TABLE_PRESENCE, presenceValues, selection,
                    mQueryContactPresenceSelectionArgs);
            if (DBG) log("seedInitialPresence: reset " + count + " presence rows to OFFLINE");

            // second: add a presence row for each contact that doesn't have a presence
            if (DBG) {
                log("seedInitialPresence: contacts_with_no_presence_selection => " +
                        CONTACTS_WITH_NO_PRESENCE_SELECTION);
            }

            c = qb.query(db,
                    CONTACT_ID_PROJECTION,
                    CONTACTS_WITH_NO_PRESENCE_SELECTION,
                    mQueryContactPresenceSelectionArgs,
                    null, null, null, null);

            if (DBG) log("seedInitialPresence: found " + c.getCount() + " contacts w/o presence");

            count = 0;

            while (c.moveToNext()) {
                long id = c.getLong(COLUMN_ID);
                presenceValues.put(Im.Presence.CONTACT_ID, id);

                try {
                    if (db.insert(TABLE_PRESENCE, null, presenceValues) > 0) {
                        count++;
                    }
                } catch (SQLiteConstraintException ex) {
                    // we could possibly catch this exception, since there could be a presence
                    // row with the same contact_id. That's fine, just ignore the error
                    if (DBG) log("seedInitialPresence: insert presence for contact_id " + id +
                            " failed, caught " + ex);
                }
            }

            db.setTransactionSuccessful();

            if (DBG) log("seedInitialPresence: added " + count + " new presence rows");
        } finally {
            c.close();
            db.endTransaction();
        }
    
public final intupdate(android.net.Uri url, android.content.ContentValues values, java.lang.String selection, java.lang.String[] selectionArgs)


        int result = 0;
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        db.beginTransaction();
        try {
            result = updateInternal(url, values, selection, selectionArgs);
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
        if (result > 0) {
            getContext().getContentResolver()
                    .notifyChange(url, null /* observer */, false /* sync */);
        }
        return result;
    
intupdateBulkContacts(android.content.ContentValues values, java.lang.String userWhere)

        ArrayList<String> usernames = values.getStringArrayList(Im.Contacts.USERNAME);
        ArrayList<String> nicknames = values.getStringArrayList(Im.Contacts.NICKNAME);

        int usernameCount = usernames.size();
        int nicknameCount = nicknames.size();

        if (usernameCount != nicknameCount) {
            Log.e(LOG_TAG, "[ImProvider] updateBulkContacts: input bundle " +
                    "username & nickname lists have diff. length!");
            return 0;
        }

        ArrayList<String> contactTypeArray = values.getStringArrayList(Im.Contacts.TYPE);
        ArrayList<String> subscriptionStatusArray =
                values.getStringArrayList(Im.Contacts.SUBSCRIPTION_STATUS);
        ArrayList<String> subscriptionTypeArray =
                values.getStringArrayList(Im.Contacts.SUBSCRIPTION_TYPE);
        ArrayList<String> quickContactArray = values.getStringArrayList(Im.Contacts.QUICK_CONTACT);
        ArrayList<String> rejectedArray = values.getStringArrayList(Im.Contacts.REJECTED);
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();

        db.beginTransaction();
        int sum = 0;

        try {
            Long provider = values.getAsLong(Im.Contacts.PROVIDER);
            Long account = values.getAsLong(Im.Contacts.ACCOUNT);

            ContentValues contactValues = new ContentValues();
            contactValues.put(Im.Contacts.PROVIDER, provider);
            contactValues.put(Im.Contacts.ACCOUNT, account);

            StringBuilder updateSelection = new StringBuilder();
            String[] updateSelectionArgs = new String[1];

            for (int i=0; i<usernameCount; i++) {
                String username = usernames.get(i);
                String nickname = nicknames.get(i);
                int type = 0;
                int subscriptionStatus = 0;
                int subscriptionType = 0;
                int quickContact = 0;
                int rejected = 0;

                try {
                    type = Integer.parseInt(contactTypeArray.get(i));
                    subscriptionStatus = Integer.parseInt(subscriptionStatusArray.get(i));
                    subscriptionType = Integer.parseInt(subscriptionTypeArray.get(i));
                    quickContact = Integer.parseInt(quickContactArray.get(i));
                    rejected = Integer.parseInt(rejectedArray.get(i));
                } catch (NumberFormatException ex) {
                    Log.e(LOG_TAG, "insertBulkContacts: caught " + ex);
                }

                if (DBG) log("updateBulkContacts[" + i + "] username=" +
                        username + ", nickname=" + nickname + ", type=" + type +
                        ", subscriptionStatus=" + subscriptionStatus + ", subscriptionType=" +
                        subscriptionType + ", qc=" + quickContact);

                contactValues.put(Im.Contacts.USERNAME, username);
                contactValues.put(Im.Contacts.NICKNAME, nickname);
                contactValues.put(Im.Contacts.TYPE, type);
                contactValues.put(Im.Contacts.SUBSCRIPTION_STATUS, subscriptionStatus);
                contactValues.put(Im.Contacts.SUBSCRIPTION_TYPE, subscriptionType);
                contactValues.put(Im.Contacts.QUICK_CONTACT, quickContact);
                contactValues.put(Im.Contacts.REJECTED, rejected);

                // append username to the selection clause
                updateSelection.delete(0, updateSelection.length());
                updateSelection.append(userWhere);
                updateSelection.append(" AND ");
                updateSelection.append(Im.Contacts.USERNAME);
                updateSelection.append("=?");

                updateSelectionArgs[0] = username;

                int numUpdated = db.update(TABLE_CONTACTS, contactValues,
                        updateSelection.toString(), updateSelectionArgs);
                if (numUpdated == 0) {
                    Log.e(LOG_TAG, "[ImProvider] updateBulkContacts: " +
                            " update failed for selection = " + updateSelection);
                } else {
                    sum += numUpdated;
                }

                // yield the lock if anyone else is trying to
                // perform a db operation here.
                db.yieldIfContended();
            }

            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }

        if (DBG) log("updateBulkContacts: " + sum + " entries updated");
        return sum;
    
private intupdateBulkPresence(android.content.ContentValues values, java.lang.String userWhere, java.lang.String[] whereArgs)

        ArrayList<String> usernames = values.getStringArrayList(Im.Contacts.USERNAME);
        int count = usernames.size();
        Long account = values.getAsLong(Im.Contacts.ACCOUNT);

        ArrayList<String> priorityArray = values.getStringArrayList(Im.Presence.PRIORITY);
        ArrayList<String> modeArray = values.getStringArrayList(Im.Presence.PRESENCE_STATUS);
        ArrayList<String> statusArray = values.getStringArrayList(
                Im.Presence.PRESENCE_CUSTOM_STATUS);
        ArrayList<String> clientTypeArray = values.getStringArrayList(Im.Presence.CLIENT_TYPE);
        ArrayList<String> resourceArray = values.getStringArrayList(Im.Presence.JID_RESOURCE);

        // append username to the selection clause
        StringBuilder buf = new StringBuilder();

        if (!TextUtils.isEmpty(userWhere)) {
            buf.append(userWhere);
            buf.append(" AND ");
        }

        buf.append(Im.Presence.CONTACT_ID);
        buf.append(" in (select ");
        buf.append(Im.Contacts._ID);
        buf.append(" from ");
        buf.append(TABLE_CONTACTS);
        buf.append(" where ");
        buf.append(Im.Contacts.ACCOUNT);
        buf.append("=? AND ");

        // use username LIKE ? for case insensitive comparison
        buf.append(Im.Contacts.USERNAME);
        buf.append(" LIKE ?) AND (");

        buf.append(Im.Presence.PRIORITY);
        buf.append("<=? OR ");
        buf.append(Im.Presence.PRIORITY);
        buf.append(" IS NULL OR ");
        buf.append(Im.Presence.JID_RESOURCE);
        buf.append("=?)");

        String selection = buf.toString();

        if (DBG) log("updateBulkPresence: selection => " + selection);

        int numArgs = (whereArgs != null ? whereArgs.length + 4 : 4);
        String[] selectionArgs = new String[numArgs];
        int selArgsIndex = 0;

        if (whereArgs != null) {
            for (selArgsIndex=0; selArgsIndex<numArgs-1; selArgsIndex++) {
                selectionArgs[selArgsIndex] = whereArgs[selArgsIndex];
            }
        }

        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();

        db.beginTransaction();
        int sum = 0;

        try {
            ContentValues presenceValues = new ContentValues();

            for (int i=0; i<count; i++) {
                String username = usernames.get(i);
                int priority = 0;
                int mode = 0;
                String status = statusArray.get(i);
                String jidResource = resourceArray == null ? "" : resourceArray.get(i);
                int clientType = Im.Presence.CLIENT_TYPE_DEFAULT;

                try {
                    if (priorityArray != null) {
                        priority = Integer.parseInt(priorityArray.get(i));
                    }
                    if (modeArray != null) {
                        mode = Integer.parseInt(modeArray.get(i));
                    }
                    if (clientTypeArray != null) {
                        clientType = Integer.parseInt(clientTypeArray.get(i));
                    }
                } catch (NumberFormatException ex) {
                    Log.e(LOG_TAG, "[ImProvider] updateBulkPresence: caught " + ex);
                }

                /*
                if (DBG) {
                    log("updateBulkPresence[" + i + "] username=" + username + ", priority=" +
                            priority + ", mode=" + mode + ", status=" + status + ", resource=" +
                            jidResource + ", clientType=" + clientType);
                }
                */

                if (modeArray != null) {
                    presenceValues.put(Im.Presence.PRESENCE_STATUS, mode);
                }
                if (priorityArray != null) {
                    presenceValues.put(Im.Presence.PRIORITY, priority);
                }
                presenceValues.put(Im.Presence.PRESENCE_CUSTOM_STATUS, status);
                if (clientTypeArray != null) {
                    presenceValues.put(Im.Presence.CLIENT_TYPE, clientType);
                }

                if (!TextUtils.isEmpty(jidResource)) {
                    presenceValues.put(Im.Presence.JID_RESOURCE, jidResource);
                }

                // fill in the selection args
                int idx = selArgsIndex;
                selectionArgs[idx++] = String.valueOf(account);
                selectionArgs[idx++] = username;
                selectionArgs[idx++] = String.valueOf(priority);
                selectionArgs[idx] = jidResource;

                int numUpdated = db.update(TABLE_PRESENCE,
                        presenceValues, selection, selectionArgs);
                if (numUpdated == 0) {
                    Log.e(LOG_TAG, "[ImProvider] updateBulkPresence: failed for " + username);
                } else {
                    sum += numUpdated;
                }

                // yield the lock if anyone else is trying to
                // perform a db operation here.
                db.yieldIfContended();
            }

            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }

        if (DBG) log("updateBulkPresence: " + sum + " entries updated");
        return sum;
    
public intupdateInternal(android.net.Uri url, android.content.ContentValues values, java.lang.String userWhere, java.lang.String[] whereArgs)

        String tableToChange;
        String idColumnName = null;
        String changedItemId = null;
        int count;

        StringBuilder whereClause = new StringBuilder();
        if(userWhere != null) {
            whereClause.append(userWhere);
        }

        boolean notifyMessagesContentUri = false;
        boolean notifyGroupMessagesContentUri = false;
        boolean notifyContactListContentUri = false;
        boolean notifyProviderAccountContentUri = false;

        int match = mUrlMatcher.match(url);
        switch (match) {
            case MATCH_PROVIDERS_BY_ID:
                changedItemId = url.getPathSegments().get(1);
                // fall through
            case MATCH_PROVIDERS:
                tableToChange = TABLE_PROVIDERS;
                break;

            case MATCH_ACCOUNTS_BY_ID:
                changedItemId = url.getPathSegments().get(1);
                // fall through
            case MATCH_ACCOUNTS:
                tableToChange = TABLE_ACCOUNTS;
                notifyProviderAccountContentUri = true;
                break;

            case MATCH_ACCOUNT_STATUS:
                changedItemId = url.getPathSegments().get(1);
                // fall through
            case MATCH_ACCOUNTS_STATUS:
                tableToChange = TABLE_ACCOUNT_STATUS;
                notifyProviderAccountContentUri = true;
                break;

            case MATCH_CONTACTS:
            case MATCH_CONTACTS_BAREBONE:
                tableToChange = TABLE_CONTACTS;
                break;

            case MATCH_CONTACTS_BY_PROVIDER:
                tableToChange = TABLE_CONTACTS;
                changedItemId = url.getPathSegments().get(2);
                idColumnName = Im.Contacts.ACCOUNT;
                break;

            case MATCH_CONTACT:
                tableToChange = TABLE_CONTACTS;
                changedItemId = url.getPathSegments().get(1);
                break;

            case MATCH_CONTACTS_BULK:
                count = updateBulkContacts(values, userWhere);
                // notify change using the "content://im/contacts" url,
                // so the change will be observed by listeners interested
                // in contacts changes.
                if (count > 0) {
                    getContext().getContentResolver().notifyChange(
                            Im.Contacts.CONTENT_URI, null);
                }
                return count;

            case MATCH_CONTACTLIST:
                tableToChange = TABLE_CONTACT_LIST;
                changedItemId = url.getPathSegments().get(1);
                notifyContactListContentUri = true;
                break;

            case MATCH_CONTACTS_ETAGS:
                tableToChange = TABLE_CONTACTS_ETAG;
                break;

            case MATCH_CONTACTS_ETAG:
                tableToChange = TABLE_CONTACTS_ETAG;
                changedItemId = url.getPathSegments().get(1);
                break;

            case MATCH_MESSAGES:
                tableToChange = TABLE_MESSAGES;
                break;

            case MATCH_MESSAGES_BY_CONTACT:
                tableToChange = TABLE_MESSAGES;
                appendWhere(whereClause, Im.Messages.ACCOUNT, "=",
                        url.getPathSegments().get(2));
                appendWhere(whereClause, Im.Messages.CONTACT, "=",
                    decodeURLSegment(url.getPathSegments().get(3)));
                notifyMessagesContentUri = true;
                break;

            case MATCH_MESSAGE:
                tableToChange = TABLE_MESSAGES;
                changedItemId = url.getPathSegments().get(1);
                notifyMessagesContentUri = true;
                break;

            case MATCH_GROUP_MESSAGES:
                tableToChange = TABLE_GROUP_MESSAGES;
                break;

            case MATCH_GROUP_MESSAGE_BY:
                tableToChange = TABLE_GROUP_MESSAGES;
                changedItemId = url.getPathSegments().get(1);
                idColumnName = Im.GroupMessages.GROUP;
                notifyGroupMessagesContentUri = true;
                break;

            case MATCH_GROUP_MESSAGE:
                tableToChange = TABLE_GROUP_MESSAGES;
                changedItemId = url.getPathSegments().get(1);
                notifyGroupMessagesContentUri = true;
                break;

            case MATCH_AVATARS:
                tableToChange = TABLE_AVATARS;
                break;

            case MATCH_AVATAR:
                tableToChange = TABLE_AVATARS;
                changedItemId = url.getPathSegments().get(1);
                break;

            case MATCH_AVATAR_BY_PROVIDER:
                tableToChange = TABLE_AVATARS;
                changedItemId = url.getPathSegments().get(2);
                idColumnName = Im.Avatars.ACCOUNT;
                break;

            case MATCH_CHATS:
                tableToChange = TABLE_CHATS;
                break;

            case MATCH_CHATS_ID:
                tableToChange = TABLE_CHATS;
                changedItemId = url.getPathSegments().get(1);
                idColumnName = Im.Chats.CONTACT_ID;
                break;

            case MATCH_PRESENCE:
                //if (DBG) log("update presence: where='" + userWhere + "'");
                tableToChange = TABLE_PRESENCE;
                break;

            case MATCH_PRESENCE_ID:
                tableToChange = TABLE_PRESENCE;
                changedItemId = url.getPathSegments().get(1);
                idColumnName = Im.Presence.CONTACT_ID;
                break;

            case MATCH_PRESENCE_BULK:
                count = updateBulkPresence(values, userWhere, whereArgs);
                // notify change using the "content://im/contacts" url,
                // so the change will be observed by listeners interested
                // in contacts changes.
                if (count > 0) {
                     getContext().getContentResolver().notifyChange(Im.Contacts.CONTENT_URI, null);
                }

                return count;

            case MATCH_INVITATION:
                tableToChange = TABLE_INVITATIONS;
                changedItemId = url.getPathSegments().get(1);
                break;

            case MATCH_SESSIONS:
                tableToChange = TABLE_SESSION_COOKIES;
                break;

            case MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME:
                tableToChange = TABLE_PROVIDER_SETTINGS;

                String providerId = url.getPathSegments().get(1);
                String name = url.getPathSegments().get(2);

                if (values.containsKey(Im.ProviderSettings.PROVIDER) ||
                        values.containsKey(Im.ProviderSettings.NAME)) {
                    throw new SecurityException("Cannot override the value for provider|name");
                }

                appendWhere(whereClause, Im.ProviderSettings.PROVIDER, "=", providerId);
                appendWhere(whereClause, Im.ProviderSettings.NAME, "=", name);

                break;

            case MATCH_OUTGOING_RMQ_MESSAGES:
                tableToChange = TABLE_OUTGOING_RMQ_MESSAGES;
                break;

            case MATCH_LAST_RMQ_ID:
                tableToChange = TABLE_LAST_RMQ_ID;
                break;

            default:
                throw new UnsupportedOperationException("Cannot update URL: " + url);
        }

        if (idColumnName == null) {
            idColumnName = "_id";
        }
        if(changedItemId != null) {
            appendWhere(whereClause, idColumnName, "=", changedItemId);
        }

        if (DBG) log("update " + url + " WHERE " + whereClause);

        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        count = db.update(tableToChange, values, whereClause.toString(), whereArgs);

        if (count > 0) {
            // In most case, we query contacts with presence and chats joined, thus
            // we should also notify that contacts changes when presence or chats changed.
            if (match == MATCH_CHATS || match == MATCH_CHATS_ID
                    || match == MATCH_PRESENCE || match == MATCH_PRESENCE_ID
                    || match == MATCH_CONTACTS_BAREBONE) {
                getContext().getContentResolver().notifyChange(Im.Contacts.CONTENT_URI, null);
            } else if (notifyMessagesContentUri) {
                if (DBG) log("notify change for " + Im.Messages.CONTENT_URI);
                getContext().getContentResolver().notifyChange(Im.Messages.CONTENT_URI, null);
            } else if (notifyGroupMessagesContentUri) {
                getContext().getContentResolver().notifyChange(Im.GroupMessages.CONTENT_URI, null);
            } else if (notifyContactListContentUri) {
                getContext().getContentResolver().notifyChange(Im.ContactList.CONTENT_URI, null);
            } else if (notifyProviderAccountContentUri) {
                if (DBG) log("notify change for " + Im.Provider.CONTENT_URI_WITH_ACCOUNT);
                getContext().getContentResolver().notifyChange(Im.Provider.CONTENT_URI_WITH_ACCOUNT,
                        null);
            }
        }

        return count;
    
private intupdateSlotForChat(long chatId, int slot)

        ContentValues values = new ContentValues();
        
        values.put(Im.Chats.SHORTCUT, slot);
        
        return update(Im.Chats.CONTENT_URI, values, Im.Chats._ID + "=?",
            new String[] { Long.toString(chatId) });