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