Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License
     15  */
     16 package com.android.providers.contacts;
     17 
     18 import android.content.ContentValues;
     19 import android.content.Context;
     20 import android.database.Cursor;
     21 import android.database.DatabaseUtils;
     22 import android.database.sqlite.SQLiteDatabase;
     23 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
     24 import android.provider.ContactsContract.Groups;
     25 import android.provider.ContactsContract.RawContacts;
     26 
     27 import com.android.providers.contacts.ContactsDatabaseHelper.Clauses;
     28 import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
     29 import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
     30 import com.android.providers.contacts.ContactsDatabaseHelper.Projections;
     31 import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
     32 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
     33 import com.android.providers.contacts.ContactsProvider2.GroupIdCacheEntry;
     34 import com.android.providers.contacts.aggregation.ContactAggregator;
     35 
     36 import java.util.ArrayList;
     37 import java.util.HashMap;
     38 
     39 /**
     40  * Handler for group membership data rows.
     41  */
     42 public class DataRowHandlerForGroupMembership extends DataRowHandler {
     43 
     44     interface RawContactsQuery {
     45         String TABLE = Tables.RAW_CONTACTS;
     46 
     47         String[] COLUMNS = new String[] {
     48                 RawContacts.DELETED,
     49                 RawContactsColumns.ACCOUNT_ID,
     50         };
     51 
     52         int DELETED = 0;
     53         int ACCOUNT_ID = 1;
     54     }
     55 
     56     private static final String SELECTION_RAW_CONTACT_ID = RawContacts._ID + "=?";
     57 
     58     private static final String QUERY_COUNT_FAVORITES_GROUP_MEMBERSHIPS_BY_RAW_CONTACT_ID =
     59             "SELECT COUNT(*) FROM " + Tables.DATA + " LEFT OUTER JOIN " + Tables .GROUPS
     60                     + " ON " + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID
     61                     + "=" + GroupsColumns.CONCRETE_ID
     62                     + " WHERE " + DataColumns.MIMETYPE_ID + "=?"
     63                     + " AND " + Tables.DATA + "." + GroupMembership.RAW_CONTACT_ID + "=?"
     64                     + " AND " + Groups.FAVORITES + "!=0";
     65 
     66     private final HashMap<String, ArrayList<GroupIdCacheEntry>> mGroupIdCache;
     67 
     68     public DataRowHandlerForGroupMembership(Context context, ContactsDatabaseHelper dbHelper,
     69             ContactAggregator aggregator,
     70             HashMap<String, ArrayList<GroupIdCacheEntry>> groupIdCache) {
     71         super(context, dbHelper, aggregator, GroupMembership.CONTENT_ITEM_TYPE);
     72         mGroupIdCache = groupIdCache;
     73     }
     74 
     75     @Override
     76     public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId,
     77             ContentValues values) {
     78         resolveGroupSourceIdInValues(txContext, rawContactId, db, values, true);
     79         long dataId = super.insert(db, txContext, rawContactId, values);
     80         if (hasFavoritesGroupMembership(db, rawContactId)) {
     81             updateRawContactsStar(db, rawContactId, true /* starred */);
     82         }
     83         updateVisibility(txContext, rawContactId);
     84         return dataId;
     85     }
     86 
     87     @Override
     88     public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
     89             Cursor c, boolean callerIsSyncAdapter) {
     90         long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
     91         boolean wasStarred = hasFavoritesGroupMembership(db, rawContactId);
     92         resolveGroupSourceIdInValues(txContext, rawContactId, db, values, false);
     93         if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
     94             return false;
     95         }
     96         boolean isStarred = hasFavoritesGroupMembership(db, rawContactId);
     97         if (wasStarred != isStarred) {
     98             updateRawContactsStar(db, rawContactId, isStarred);
     99         }
    100         updateVisibility(txContext, rawContactId);
    101         return true;
    102     }
    103 
    104     private void updateRawContactsStar(SQLiteDatabase db, long rawContactId, boolean starred) {
    105         ContentValues rawContactValues = new ContentValues();
    106         rawContactValues.put(RawContacts.STARRED, starred ? 1 : 0);
    107         if (db.update(Tables.RAW_CONTACTS, rawContactValues, SELECTION_RAW_CONTACT_ID,
    108                 new String[]{Long.toString(rawContactId)}) > 0) {
    109             mContactAggregator.updateStarred(rawContactId);
    110         }
    111     }
    112 
    113     private boolean hasFavoritesGroupMembership(SQLiteDatabase db, long rawContactId) {
    114         // TODO compiled SQL statement
    115         final long groupMembershipMimetypeId = mDbHelper
    116                 .getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
    117         boolean isStarred = 0 < DatabaseUtils
    118                 .longForQuery(db, QUERY_COUNT_FAVORITES_GROUP_MEMBERSHIPS_BY_RAW_CONTACT_ID,
    119                 new String[]{Long.toString(groupMembershipMimetypeId), Long.toString(rawContactId)});
    120         return isStarred;
    121     }
    122 
    123     @Override
    124     public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) {
    125         long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID);
    126         boolean wasStarred = hasFavoritesGroupMembership(db, rawContactId);
    127         int count = super.delete(db, txContext, c);
    128         boolean isStarred = hasFavoritesGroupMembership(db, rawContactId);
    129         if (wasStarred && !isStarred) {
    130             updateRawContactsStar(db, rawContactId, false /* starred */);
    131         }
    132         updateVisibility(txContext, rawContactId);
    133         return count;
    134     }
    135 
    136     private void updateVisibility(TransactionContext txContext, long rawContactId) {
    137         long contactId = mDbHelper.getContactId(rawContactId);
    138         if (contactId == 0) {
    139             return;
    140         }
    141 
    142         if (mDbHelper.updateContactVisibleOnlyIfChanged(txContext, contactId)) {
    143             mContactAggregator.updateAggregationAfterVisibilityChange(contactId);
    144         }
    145     }
    146 
    147     private void resolveGroupSourceIdInValues(TransactionContext txContext,
    148             long rawContactId, SQLiteDatabase db, ContentValues values, boolean isInsert) {
    149         boolean containsGroupSourceId = values.containsKey(GroupMembership.GROUP_SOURCE_ID);
    150         boolean containsGroupId = values.containsKey(GroupMembership.GROUP_ROW_ID);
    151         if (containsGroupSourceId && containsGroupId) {
    152             throw new IllegalArgumentException(
    153                     "you are not allowed to set both the GroupMembership.GROUP_SOURCE_ID "
    154                             + "and GroupMembership.GROUP_ROW_ID");
    155         }
    156 
    157         if (!containsGroupSourceId && !containsGroupId) {
    158             if (isInsert) {
    159                 throw new IllegalArgumentException(
    160                         "you must set exactly one of GroupMembership.GROUP_SOURCE_ID "
    161                                 + "and GroupMembership.GROUP_ROW_ID");
    162             } else {
    163                 return;
    164             }
    165         }
    166 
    167         if (containsGroupSourceId) {
    168             final String sourceId = values.getAsString(GroupMembership.GROUP_SOURCE_ID);
    169             final long groupId = getOrMakeGroup(db, rawContactId, sourceId,
    170                     txContext.getAccountIdOrNullForRawContact(rawContactId));
    171             values.remove(GroupMembership.GROUP_SOURCE_ID);
    172             values.put(GroupMembership.GROUP_ROW_ID, groupId);
    173         }
    174     }
    175 
    176     /**
    177      * Returns the group id of the group with sourceId and the same account as rawContactId.
    178      * If the group doesn't already exist then it is first created.
    179      *
    180      * @param db SQLiteDatabase to use for this operation
    181      * @param rawContactId the raw contact this group is associated with
    182      * @param sourceId the source ID of the group to query or create
    183      * @param accountIdOrNull the account ID for the raw contact.  If null it'll be queried from
    184      *    the raw_contacts table.
    185      * @return the group id of the existing or created group
    186      * @throws IllegalArgumentException if the contact is not associated with an account
    187      * @throws IllegalStateException if a group needs to be created but the creation failed
    188      */
    189     private long getOrMakeGroup(SQLiteDatabase db, long rawContactId, String sourceId,
    190             Long accountIdOrNull) {
    191 
    192         if (accountIdOrNull == null) {
    193             mSelectionArgs1[0] = String.valueOf(rawContactId);
    194             Cursor c = db.query(RawContactsQuery.TABLE, RawContactsQuery.COLUMNS,
    195                     RawContactsColumns.CONCRETE_ID + "=?", mSelectionArgs1, null, null, null);
    196             try {
    197                 if (c.moveToFirst()) {
    198                     accountIdOrNull = c.getLong(RawContactsQuery.ACCOUNT_ID);
    199                 }
    200             } finally {
    201                 c.close();
    202             }
    203         }
    204 
    205         if (accountIdOrNull == null) {
    206             throw new IllegalArgumentException("Raw contact not found for _ID=" + rawContactId);
    207         }
    208         final long accountId = accountIdOrNull;
    209 
    210         ArrayList<GroupIdCacheEntry> entries = mGroupIdCache.get(sourceId);
    211         if (entries == null) {
    212             entries = new ArrayList<GroupIdCacheEntry>(1);
    213             mGroupIdCache.put(sourceId, entries);
    214         }
    215 
    216         int count = entries.size();
    217         for (int i = 0; i < count; i++) {
    218             GroupIdCacheEntry entry = entries.get(i);
    219             if (entry.accountId == accountId) {
    220                 return entry.groupId;
    221             }
    222         }
    223 
    224         GroupIdCacheEntry entry = new GroupIdCacheEntry();
    225         entry.accountId = accountId;
    226         entry.sourceId = sourceId;
    227         entries.add(0, entry);
    228 
    229         // look up the group that contains this sourceId and has the same account as the contact
    230         // referred to by rawContactId
    231         Cursor c = db.query(Tables.GROUPS, Projections.ID,
    232                 Clauses.GROUP_HAS_ACCOUNT_AND_SOURCE_ID,
    233                 new String[]{sourceId, Long.toString(accountId)}, null, null, null);
    234 
    235         try {
    236             if (c.moveToFirst()) {
    237                 entry.groupId = c.getLong(0);
    238             } else {
    239                 ContentValues groupValues = new ContentValues();
    240                 groupValues.put(GroupsColumns.ACCOUNT_ID, accountId);
    241                 groupValues.put(Groups.SOURCE_ID, sourceId);
    242                 long groupId = db.insert(Tables.GROUPS, null, groupValues);
    243                 if (groupId < 0) {
    244                     throw new IllegalStateException("unable to create a new group with "
    245                             + "this sourceid: " + groupValues);
    246                 }
    247                 entry.groupId = groupId;
    248             }
    249         } finally {
    250             c.close();
    251         }
    252 
    253         return entry.groupId;
    254     }
    255 }
    256