PageRenderTime 58ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/src/com/android/providers/contacts/DataRowHandler.java

https://gitlab.com/Atomic-ROM/packages_providers_ContactsProvider
Java | 376 lines | 271 code | 44 blank | 61 comment | 73 complexity | 06fdf5f44b19209e1cf415394d53cc82 MD5 | raw file
  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. import android.content.ContentValues;
  18. import android.content.Context;
  19. import android.database.Cursor;
  20. import android.database.sqlite.SQLiteDatabase;
  21. import android.provider.ContactsContract.CommonDataKinds.Email;
  22. import android.provider.ContactsContract.CommonDataKinds.Nickname;
  23. import android.provider.ContactsContract.CommonDataKinds.Organization;
  24. import android.provider.ContactsContract.CommonDataKinds.Phone;
  25. import android.provider.ContactsContract.CommonDataKinds.StructuredName;
  26. import android.provider.ContactsContract.Data;
  27. import android.text.TextUtils;
  28. import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
  29. import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
  30. import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
  31. import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
  32. import com.android.providers.contacts.aggregation.AbstractContactAggregator;
  33. /**
  34. * Handles inserts and update for a specific Data type.
  35. */
  36. public abstract class DataRowHandler {
  37. public interface DataDeleteQuery {
  38. public static final String TABLE = Tables.DATA_JOIN_MIMETYPES;
  39. public static final String[] CONCRETE_COLUMNS = new String[] {
  40. DataColumns.CONCRETE_ID,
  41. MimetypesColumns.MIMETYPE,
  42. Data.RAW_CONTACT_ID,
  43. Data.IS_PRIMARY,
  44. Data.DATA1,
  45. };
  46. public static final String[] COLUMNS = new String[] {
  47. Data._ID,
  48. MimetypesColumns.MIMETYPE,
  49. Data.RAW_CONTACT_ID,
  50. Data.IS_PRIMARY,
  51. Data.DATA1,
  52. };
  53. public static final int _ID = 0;
  54. public static final int MIMETYPE = 1;
  55. public static final int RAW_CONTACT_ID = 2;
  56. public static final int IS_PRIMARY = 3;
  57. public static final int DATA1 = 4;
  58. }
  59. public interface DataUpdateQuery {
  60. String[] COLUMNS = { Data._ID, Data.RAW_CONTACT_ID, Data.MIMETYPE };
  61. int _ID = 0;
  62. int RAW_CONTACT_ID = 1;
  63. int MIMETYPE = 2;
  64. }
  65. protected final Context mContext;
  66. protected final ContactsDatabaseHelper mDbHelper;
  67. protected final AbstractContactAggregator mContactAggregator;
  68. protected String[] mSelectionArgs1 = new String[1];
  69. protected final String mMimetype;
  70. protected long mMimetypeId;
  71. @SuppressWarnings("all")
  72. public DataRowHandler(Context context, ContactsDatabaseHelper dbHelper,
  73. AbstractContactAggregator aggregator, String mimetype) {
  74. mContext = context;
  75. mDbHelper = dbHelper;
  76. mContactAggregator = aggregator;
  77. mMimetype = mimetype;
  78. // To ensure the data column position. This is dead code if properly configured.
  79. if (StructuredName.DISPLAY_NAME != Data.DATA1 || Nickname.NAME != Data.DATA1
  80. || Organization.COMPANY != Data.DATA1 || Phone.NUMBER != Data.DATA1
  81. || Email.DATA != Data.DATA1) {
  82. throw new AssertionError("Some of ContactsContract.CommonDataKinds class primary"
  83. + " data is not in DATA1 column");
  84. }
  85. }
  86. protected long getMimeTypeId() {
  87. if (mMimetypeId == 0) {
  88. mMimetypeId = mDbHelper.getMimeTypeId(mMimetype);
  89. }
  90. return mMimetypeId;
  91. }
  92. /**
  93. * Inserts a row into the {@link Data} table.
  94. */
  95. public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId,
  96. ContentValues values) {
  97. final long dataId = db.insert(Tables.DATA, null, values);
  98. final Integer primary = values.getAsInteger(Data.IS_PRIMARY);
  99. final Integer superPrimary = values.getAsInteger(Data.IS_SUPER_PRIMARY);
  100. if ((primary != null && primary != 0) || (superPrimary != null && superPrimary != 0)) {
  101. final long mimeTypeId = getMimeTypeId();
  102. mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId);
  103. // We also have to make sure that no other data item on this raw_contact is
  104. // configured super primary
  105. if (superPrimary != null) {
  106. if (superPrimary != 0) {
  107. mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId);
  108. } else {
  109. mDbHelper.clearSuperPrimary(rawContactId, mimeTypeId);
  110. }
  111. } else {
  112. // if there is already another data item configured as super-primary,
  113. // take over the flag (which will automatically remove it from the other item)
  114. if (mDbHelper.rawContactHasSuperPrimary(rawContactId, mimeTypeId)) {
  115. mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId);
  116. }
  117. }
  118. }
  119. if (containsSearchableColumns(values)) {
  120. txContext.invalidateSearchIndexForRawContact(rawContactId);
  121. }
  122. return dataId;
  123. }
  124. /**
  125. * Validates data and updates a {@link Data} row using the cursor, which contains
  126. * the current data.
  127. *
  128. * @return true if update changed something
  129. */
  130. public boolean update(SQLiteDatabase db, TransactionContext txContext,
  131. ContentValues values, Cursor c, boolean callerIsSyncAdapter) {
  132. long dataId = c.getLong(DataUpdateQuery._ID);
  133. long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
  134. handlePrimaryAndSuperPrimary(values, dataId, rawContactId);
  135. if (values.size() > 0) {
  136. mSelectionArgs1[0] = String.valueOf(dataId);
  137. db.update(Tables.DATA, values, Data._ID + " =?", mSelectionArgs1);
  138. }
  139. if (containsSearchableColumns(values)) {
  140. txContext.invalidateSearchIndexForRawContact(rawContactId);
  141. }
  142. txContext.markRawContactDirtyAndChanged(rawContactId, callerIsSyncAdapter);
  143. return true;
  144. }
  145. public boolean hasSearchableData() {
  146. return false;
  147. }
  148. public boolean containsSearchableColumns(ContentValues values) {
  149. return false;
  150. }
  151. public void appendSearchableData(SearchIndexManager.IndexBuilder builder) {
  152. }
  153. /**
  154. * Ensures that all super-primary and primary flags of this raw_contact are
  155. * configured correctly
  156. */
  157. private void handlePrimaryAndSuperPrimary(ContentValues values, long dataId,
  158. long rawContactId) {
  159. final boolean hasPrimary = values.getAsInteger(Data.IS_PRIMARY) != null;
  160. final boolean hasSuperPrimary = values.getAsInteger(Data.IS_SUPER_PRIMARY) != null;
  161. // Nothing to do? Bail out early
  162. if (!hasPrimary && !hasSuperPrimary) return;
  163. final long mimeTypeId = getMimeTypeId();
  164. // Check if we want to clear values
  165. final boolean clearPrimary = hasPrimary &&
  166. values.getAsInteger(Data.IS_PRIMARY) == 0;
  167. final boolean clearSuperPrimary = hasSuperPrimary &&
  168. values.getAsInteger(Data.IS_SUPER_PRIMARY) == 0;
  169. if (clearPrimary || clearSuperPrimary) {
  170. // Test whether these values are currently set
  171. mSelectionArgs1[0] = String.valueOf(dataId);
  172. final String[] cols = new String[] { Data.IS_PRIMARY, Data.IS_SUPER_PRIMARY };
  173. final Cursor c = mDbHelper.getReadableDatabase().query(Tables.DATA,
  174. cols, Data._ID + "=?", mSelectionArgs1, null, null, null);
  175. try {
  176. if (c.moveToFirst()) {
  177. final boolean isPrimary = c.getInt(0) != 0;
  178. final boolean isSuperPrimary = c.getInt(1) != 0;
  179. // Clear values if they are currently set
  180. if (isSuperPrimary) {
  181. mDbHelper.clearSuperPrimary(rawContactId, mimeTypeId);
  182. }
  183. if (clearPrimary && isPrimary) {
  184. mDbHelper.setIsPrimary(rawContactId, -1, mimeTypeId);
  185. }
  186. }
  187. } finally {
  188. c.close();
  189. }
  190. } else {
  191. // Check if we want to set values
  192. final boolean setPrimary = hasPrimary &&
  193. values.getAsInteger(Data.IS_PRIMARY) != 0;
  194. final boolean setSuperPrimary = hasSuperPrimary &&
  195. values.getAsInteger(Data.IS_SUPER_PRIMARY) != 0;
  196. if (setSuperPrimary) {
  197. // Set both super primary and primary
  198. mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId);
  199. mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId);
  200. } else if (setPrimary) {
  201. // Primary was explicitly set, but super-primary was not.
  202. // In this case we set super-primary on this data item, if
  203. // any data item of the same raw-contact already is super-primary
  204. if (mDbHelper.rawContactHasSuperPrimary(rawContactId, mimeTypeId)) {
  205. mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId);
  206. }
  207. mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId);
  208. }
  209. }
  210. // Now that we've taken care of clearing this, remove it from "values".
  211. values.remove(Data.IS_SUPER_PRIMARY);
  212. values.remove(Data.IS_PRIMARY);
  213. }
  214. public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) {
  215. long dataId = c.getLong(DataDeleteQuery._ID);
  216. long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID);
  217. boolean primary = c.getInt(DataDeleteQuery.IS_PRIMARY) != 0;
  218. mSelectionArgs1[0] = String.valueOf(dataId);
  219. int count = db.delete(Tables.DATA, Data._ID + "=?", mSelectionArgs1);
  220. mSelectionArgs1[0] = String.valueOf(rawContactId);
  221. db.delete(Tables.PRESENCE, PresenceColumns.RAW_CONTACT_ID + "=?", mSelectionArgs1);
  222. if (count != 0 && primary) {
  223. fixPrimary(db, rawContactId);
  224. }
  225. if (hasSearchableData()) {
  226. txContext.invalidateSearchIndexForRawContact(rawContactId);
  227. }
  228. return count;
  229. }
  230. private void fixPrimary(SQLiteDatabase db, long rawContactId) {
  231. long mimeTypeId = getMimeTypeId();
  232. long primaryId = -1;
  233. int primaryType = -1;
  234. mSelectionArgs1[0] = String.valueOf(rawContactId);
  235. Cursor c = db.query(DataDeleteQuery.TABLE,
  236. DataDeleteQuery.CONCRETE_COLUMNS,
  237. Data.RAW_CONTACT_ID + "=?" +
  238. " AND " + DataColumns.MIMETYPE_ID + "=" + mimeTypeId,
  239. mSelectionArgs1, null, null, null);
  240. try {
  241. while (c.moveToNext()) {
  242. long dataId = c.getLong(DataDeleteQuery._ID);
  243. int type = c.getInt(DataDeleteQuery.DATA1);
  244. if (primaryType == -1 || getTypeRank(type) < getTypeRank(primaryType)) {
  245. primaryId = dataId;
  246. primaryType = type;
  247. }
  248. }
  249. } finally {
  250. c.close();
  251. }
  252. if (primaryId != -1) {
  253. mDbHelper.setIsPrimary(rawContactId, primaryId, mimeTypeId);
  254. }
  255. }
  256. /**
  257. * Returns the rank of a specific record type to be used in determining the primary
  258. * row. Lower number represents higher priority.
  259. */
  260. protected int getTypeRank(int type) {
  261. return 0;
  262. }
  263. protected void fixRawContactDisplayName(SQLiteDatabase db, TransactionContext txContext,
  264. long rawContactId) {
  265. if (!isNewRawContact(txContext, rawContactId)) {
  266. mDbHelper.updateRawContactDisplayName(db, rawContactId);
  267. mContactAggregator.updateDisplayNameForRawContact(db, rawContactId);
  268. }
  269. }
  270. private boolean isNewRawContact(TransactionContext txContext, long rawContactId) {
  271. return txContext.isNewRawContact(rawContactId);
  272. }
  273. /**
  274. * Return set of values, using current values at given {@link Data#_ID}
  275. * as baseline, but augmented with any updates. Returns null if there is
  276. * no change.
  277. */
  278. public ContentValues getAugmentedValues(SQLiteDatabase db, long dataId,
  279. ContentValues update) {
  280. boolean changing = false;
  281. final ContentValues values = new ContentValues();
  282. mSelectionArgs1[0] = String.valueOf(dataId);
  283. final Cursor cursor = db.query(Tables.DATA, null, Data._ID + "=?",
  284. mSelectionArgs1, null, null, null);
  285. try {
  286. if (cursor.moveToFirst()) {
  287. for (int i = 0; i < cursor.getColumnCount(); i++) {
  288. final String key = cursor.getColumnName(i);
  289. final String value = cursor.getString(i);
  290. if (!changing && update.containsKey(key)) {
  291. Object newValue = update.get(key);
  292. String newString = newValue == null ? null : newValue.toString();
  293. changing |= !TextUtils.equals(newString, value);
  294. }
  295. values.put(key, value);
  296. }
  297. }
  298. } finally {
  299. cursor.close();
  300. }
  301. if (!changing) {
  302. return null;
  303. }
  304. values.putAll(update);
  305. return values;
  306. }
  307. public void triggerAggregation(TransactionContext txContext, long rawContactId) {
  308. mContactAggregator.triggerAggregation(txContext, rawContactId);
  309. }
  310. /**
  311. * Test all against {@link TextUtils#isEmpty(CharSequence)}.
  312. */
  313. public boolean areAllEmpty(ContentValues values, String[] keys) {
  314. for (String key : keys) {
  315. if (!TextUtils.isEmpty(values.getAsString(key))) {
  316. return false;
  317. }
  318. }
  319. return true;
  320. }
  321. /**
  322. * Returns true if a value (possibly null) is specified for at least one of the supplied keys.
  323. */
  324. public boolean areAnySpecified(ContentValues values, String[] keys) {
  325. for (String key : keys) {
  326. if (values.containsKey(key)) {
  327. return true;
  328. }
  329. }
  330. return false;
  331. }
  332. }