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) { 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)) { 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