• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.AbstractContactAggregator;
35 
36 import java.util.ArrayList;
37 import java.util.Map;
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 Map<String, ArrayList<GroupIdCacheEntry>> mGroupIdCache;
67 
DataRowHandlerForGroupMembership(Context context, ContactsDatabaseHelper dbHelper, AbstractContactAggregator aggregator, Map<String, ArrayList<GroupIdCacheEntry>> groupIdCache)68     public DataRowHandlerForGroupMembership(Context context, ContactsDatabaseHelper dbHelper,
69             AbstractContactAggregator aggregator,
70             Map<String, ArrayList<GroupIdCacheEntry>> groupIdCache) {
71         super(context, dbHelper, aggregator, GroupMembership.CONTENT_ITEM_TYPE);
72         mGroupIdCache = groupIdCache;
73     }
74 
75     @Override
insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, ContentValues values)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
update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, Cursor c, boolean callerIsSyncAdapter)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 
updateRawContactsStar(SQLiteDatabase db, long rawContactId, boolean starred)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 
hasFavoritesGroupMembership(SQLiteDatabase db, long rawContactId)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