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