• 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");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.providers.contacts;
18 
19 import static com.android.providers.contacts.flags.Flags.cp2SyncSearchIndexFlag;
20 
21 import android.database.Cursor;
22 import android.database.sqlite.SQLiteDatabase;
23 import android.database.sqlite.SQLiteStatement;
24 import android.util.ArrayMap;
25 import android.util.ArraySet;
26 
27 import java.util.Map.Entry;
28 import java.util.Set;
29 
30 /**
31  * Accumulates information for an entire transaction. {@link ContactsProvider2} consumes
32  * it at commit time.
33  */
34 public class TransactionContext  {
35 
36     private final boolean mForProfile;
37     /** Map from raw contact id to account Id */
38     private ArrayMap<Long, Long> mInsertedRawContactsAccounts;
39     private ArraySet<Long> mUpdatedRawContacts;
40     private ArraySet<Long> mBackupIdChangedRawContacts;
41     private ArraySet<Long> mDirtyRawContacts;
42     // Set used to track what has been changed and deleted. This is needed so we can update the
43     // contact last touch timestamp.  Dirty set above is only set when sync adapter is false.
44     // {@see android.provider.ContactsContract#CALLER_IS_SYNCADAPTER}. While the set below will
45     // contain all changed contacts.
46     private ArraySet<Long> mChangedRawContacts;
47     private ArraySet<Long> mStaleSearchIndexRawContacts;
48     private ArraySet<Long> mStaleSearchIndexContacts;
49     private ArrayMap<Long, Object> mUpdatedSyncStates;
50 
TransactionContext(boolean forProfile)51     public TransactionContext(boolean forProfile) {
52         mForProfile = forProfile;
53     }
54 
isForProfile()55     public boolean isForProfile() {
56         return mForProfile;
57     }
58 
rawContactInserted(long rawContactId, long accountId)59     public void rawContactInserted(long rawContactId, long accountId) {
60         if (mInsertedRawContactsAccounts == null) mInsertedRawContactsAccounts = new ArrayMap<>();
61         mInsertedRawContactsAccounts.put(rawContactId, accountId);
62 
63         markRawContactChangedOrDeletedOrInserted(rawContactId);
64     }
65 
rawContactUpdated(long rawContactId)66     public void rawContactUpdated(long rawContactId) {
67         if (mUpdatedRawContacts == null) mUpdatedRawContacts = new ArraySet<>();
68         mUpdatedRawContacts.add(rawContactId);
69     }
70 
markRawContactDirtyAndChanged(long rawContactId, boolean isSyncAdapter)71     public void markRawContactDirtyAndChanged(long rawContactId, boolean isSyncAdapter) {
72         if (!isSyncAdapter) {
73             if (mDirtyRawContacts == null) {
74                 mDirtyRawContacts = new ArraySet<>();
75             }
76             mDirtyRawContacts.add(rawContactId);
77         }
78 
79         markRawContactChangedOrDeletedOrInserted(rawContactId);
80     }
81 
markRawContactChangedOrDeletedOrInserted(long rawContactId)82     public void markRawContactChangedOrDeletedOrInserted(long rawContactId) {
83         if (mChangedRawContacts == null) {
84             mChangedRawContacts = new ArraySet<>();
85         }
86         mChangedRawContacts.add(rawContactId);
87     }
88 
syncStateUpdated(long rowId, Object data)89     public void syncStateUpdated(long rowId, Object data) {
90         if (mUpdatedSyncStates == null) mUpdatedSyncStates = new ArrayMap<>();
91         mUpdatedSyncStates.put(rowId, data);
92     }
93 
invalidateSearchIndexForRawContact(SQLiteDatabase db, long rawContactId)94     public void invalidateSearchIndexForRawContact(SQLiteDatabase db, long rawContactId) {
95         if (!cp2SyncSearchIndexFlag()) {
96             if (mStaleSearchIndexRawContacts == null) {
97                 mStaleSearchIndexRawContacts = new ArraySet<>();
98             }
99             mStaleSearchIndexRawContacts.add(rawContactId);
100             return;
101         }
102         createStaleSearchIndexTableIfNotExists(db);
103         db.execSQL("""
104                 INSERT OR IGNORE INTO stale_search_index_contacts
105                     SELECT raw_contacts.contact_id
106                     FROM raw_contacts
107                     WHERE raw_contacts._id = ?""",
108                 new Long[]{rawContactId});
109     }
110 
invalidateSearchIndexForContact(SQLiteDatabase db, long contactId)111     public void invalidateSearchIndexForContact(SQLiteDatabase db, long contactId) {
112         if (!cp2SyncSearchIndexFlag()) {
113             if (mStaleSearchIndexContacts == null) mStaleSearchIndexContacts = new ArraySet<>();
114             mStaleSearchIndexContacts.add(contactId);
115             return;
116         }
117         createStaleSearchIndexTableIfNotExists(db);
118         db.execSQL("INSERT OR IGNORE INTO stale_search_index_contacts VALUES (?)",
119                 new Long[]{contactId});
120     }
121 
getInsertedRawContactIds()122     public Set<Long> getInsertedRawContactIds() {
123         if (mInsertedRawContactsAccounts == null) mInsertedRawContactsAccounts = new ArrayMap<>();
124         return mInsertedRawContactsAccounts.keySet();
125     }
126 
getUpdatedRawContactIds()127     public Set<Long> getUpdatedRawContactIds() {
128         if (mUpdatedRawContacts == null) mUpdatedRawContacts = new ArraySet<>();
129         return mUpdatedRawContacts;
130     }
131 
getDirtyRawContactIds()132     public Set<Long> getDirtyRawContactIds() {
133         if (mDirtyRawContacts == null) mDirtyRawContacts = new ArraySet<>();
134         return mDirtyRawContacts;
135     }
136 
getChangedRawContactIds()137     public Set<Long> getChangedRawContactIds() {
138         if (mChangedRawContacts == null) mChangedRawContacts = new ArraySet<>();
139         return mChangedRawContacts;
140     }
141 
getStaleSearchIndexRawContactIds()142     public Set<Long> getStaleSearchIndexRawContactIds() {
143         if (cp2SyncSearchIndexFlag()) {
144             throw new UnsupportedOperationException();
145         }
146         if (mStaleSearchIndexRawContacts == null) mStaleSearchIndexRawContacts = new ArraySet<>();
147         return mStaleSearchIndexRawContacts;
148     }
149 
getStaleSearchIndexContactIds()150     public Set<Long> getStaleSearchIndexContactIds() {
151         if (cp2SyncSearchIndexFlag()) {
152             throw new UnsupportedOperationException();
153         }
154         if (mStaleSearchIndexContacts == null) mStaleSearchIndexContacts = new ArraySet<>();
155         return mStaleSearchIndexContacts;
156     }
157 
getStaleSearchIndexContactIdsCount(SQLiteDatabase db)158     public long getStaleSearchIndexContactIdsCount(SQLiteDatabase db) {
159         createStaleSearchIndexTableIfNotExists(db);
160         try (Cursor cursor =
161                 db.rawQuery("SELECT COUNT(*) FROM stale_search_index_contacts", null)) {
162             return cursor.moveToFirst() ? cursor.getLong(0) : 0;
163         }
164     }
165 
getUpdatedSyncStates()166     public Set<Entry<Long, Object>> getUpdatedSyncStates() {
167         if (mUpdatedSyncStates == null) mUpdatedSyncStates = new ArrayMap<>();
168         return mUpdatedSyncStates.entrySet();
169     }
170 
getAccountIdOrNullForRawContact(long rawContactId)171     public Long getAccountIdOrNullForRawContact(long rawContactId) {
172         if (mInsertedRawContactsAccounts == null) mInsertedRawContactsAccounts = new ArrayMap<>();
173         return mInsertedRawContactsAccounts.get(rawContactId);
174     }
175 
isNewRawContact(long rawContactId)176     public boolean isNewRawContact(long rawContactId) {
177         if (mInsertedRawContactsAccounts == null) mInsertedRawContactsAccounts = new ArrayMap<>();
178         return mInsertedRawContactsAccounts.containsKey(rawContactId);
179     }
180 
clearExceptSearchIndexUpdates()181     public void clearExceptSearchIndexUpdates() {
182         mInsertedRawContactsAccounts = null;
183         mUpdatedRawContacts = null;
184         mUpdatedSyncStates = null;
185         mDirtyRawContacts = null;
186         mChangedRawContacts = null;
187         mBackupIdChangedRawContacts = null;
188     }
189 
clearSearchIndexUpdates(SQLiteDatabase db)190     public void clearSearchIndexUpdates(SQLiteDatabase db) {
191         if (cp2SyncSearchIndexFlag()) {
192             db.delete("stale_search_index_contacts", null, null);
193         } else {
194             mStaleSearchIndexRawContacts = null;
195             mStaleSearchIndexContacts = null;
196         }
197     }
198 
clearAll(SQLiteDatabase db)199     public void clearAll(SQLiteDatabase db) {
200         clearExceptSearchIndexUpdates();
201         clearSearchIndexUpdates(db);
202     }
203 
createStaleSearchIndexTableIfNotExists(SQLiteDatabase db)204     private void createStaleSearchIndexTableIfNotExists(SQLiteDatabase db) {
205         // Given the SQL query is a DDL statement if one uses SQLiteDatabase#execSQL
206         // to run it, it will trigger a clearing of the SQLite prepared statement cache.
207         // Clearing the cache results in worst performance when running recurring SQL
208         // queries. For this reason prefer to use a pre-compiled SQL statement, which
209         // bypasses the cache clearing.
210         try (SQLiteStatement statement = db.compileStatement("""
211                 CREATE TEMP TABLE IF NOT EXISTS
212                  stale_search_index_contacts (id INTEGER PRIMARY KEY)""")) {
213             statement.execute();
214         }
215     }
216 }
217