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 */ 16package com.android.providers.contacts; 17 18import android.content.ContentValues; 19import android.content.Context; 20import android.database.Cursor; 21import android.database.DatabaseUtils; 22import android.database.sqlite.SQLiteDatabase; 23import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 24import android.provider.ContactsContract.Groups; 25import android.provider.ContactsContract.RawContacts; 26 27import com.android.providers.contacts.ContactsDatabaseHelper.Clauses; 28import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns; 29import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns; 30import com.android.providers.contacts.ContactsDatabaseHelper.Projections; 31import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns; 32import com.android.providers.contacts.ContactsDatabaseHelper.Tables; 33import com.android.providers.contacts.ContactsProvider2.GroupIdCacheEntry; 34import com.android.providers.contacts.aggregation.ContactAggregator; 35 36import java.util.ArrayList; 37import java.util.HashMap; 38 39/** 40 * Handler for group membership data rows. 41 */ 42public 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