1 /* 2 * Copyright (C) 2011 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 android.content.ContentValues; 20 import android.database.Cursor; 21 import android.database.sqlite.SQLiteDatabase; 22 import android.provider.ContactsContract; 23 import android.provider.ContactsContract.RawContacts; 24 import android.test.MoreAsserts; 25 import android.test.suitebuilder.annotation.LargeTest; 26 import android.test.suitebuilder.annotation.SmallTest; 27 28 import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns; 29 import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns; 30 import com.android.providers.contacts.ContactsDatabaseHelper.Tables; 31 import com.google.android.collect.Sets; 32 33 import java.util.HashSet; 34 import java.util.Set; 35 36 @SmallTest 37 public class ContactsDatabaseHelperTest extends BaseContactsProvider2Test { 38 private ContactsDatabaseHelper mDbHelper; 39 private SQLiteDatabase mDb; 40 41 @Override setUp()42 protected void setUp() throws Exception { 43 super.setUp(); 44 mDbHelper = getContactsProvider().getDatabaseHelper(getContext()); 45 mDb = mDbHelper.getWritableDatabase(); 46 } 47 testGetOrCreateAccountId()48 public void testGetOrCreateAccountId() { 49 final AccountWithDataSet a1 = null; 50 final AccountWithDataSet a2 = new AccountWithDataSet("a", null, null); 51 final AccountWithDataSet a3 = new AccountWithDataSet(null, "b", null); 52 final AccountWithDataSet a4 = new AccountWithDataSet(null, null, "c"); 53 final AccountWithDataSet a5 = new AccountWithDataSet("a", "b", "c"); 54 55 // First, there's no accounts. getAccountIdOrNull() always returns null. 56 assertNull(mDbHelper.getAccountIdOrNull(a1)); 57 assertNull(mDbHelper.getAccountIdOrNull(a2)); 58 assertNull(mDbHelper.getAccountIdOrNull(a3)); 59 assertNull(mDbHelper.getAccountIdOrNull(a4)); 60 assertNull(mDbHelper.getAccountIdOrNull(a5)); 61 62 // getOrCreateAccountId should create accounts. 63 final long a1id = mDbHelper.getOrCreateAccountIdInTransaction(a1); 64 final long a2id = mDbHelper.getOrCreateAccountIdInTransaction(a2); 65 final long a3id = mDbHelper.getOrCreateAccountIdInTransaction(a3); 66 final long a4id = mDbHelper.getOrCreateAccountIdInTransaction(a4); 67 final long a5id = mDbHelper.getOrCreateAccountIdInTransaction(a5); 68 69 // The IDs should be all positive and unique. 70 assertTrue(a1id > 0); 71 assertTrue(a2id > 0); 72 assertTrue(a3id > 0); 73 assertTrue(a4id > 0); 74 assertTrue(a5id > 0); 75 76 final Set<Long> ids = Sets.newHashSet(); 77 ids.add(a1id); 78 ids.add(a2id); 79 ids.add(a3id); 80 ids.add(a4id); 81 ids.add(a5id); 82 assertEquals(5, ids.size()); 83 84 // Second call: This time getOrCreateAccountId will return the existing IDs. 85 assertEquals(a1id, mDbHelper.getOrCreateAccountIdInTransaction(a1)); 86 assertEquals(a2id, mDbHelper.getOrCreateAccountIdInTransaction(a2)); 87 assertEquals(a3id, mDbHelper.getOrCreateAccountIdInTransaction(a3)); 88 assertEquals(a4id, mDbHelper.getOrCreateAccountIdInTransaction(a4)); 89 assertEquals(a5id, mDbHelper.getOrCreateAccountIdInTransaction(a5)); 90 91 // Now getAccountIdOrNull() returns IDs too. 92 assertEquals((Long) a1id, mDbHelper.getAccountIdOrNull(a1)); 93 assertEquals((Long) a2id, mDbHelper.getAccountIdOrNull(a2)); 94 assertEquals((Long) a3id, mDbHelper.getAccountIdOrNull(a3)); 95 assertEquals((Long) a4id, mDbHelper.getAccountIdOrNull(a4)); 96 assertEquals((Long) a5id, mDbHelper.getAccountIdOrNull(a5)); 97 98 // null and AccountWithDataSet.NULL should be treated as the same thing. 99 assertEquals(a1id, mDbHelper.getOrCreateAccountIdInTransaction(AccountWithDataSet.LOCAL)); 100 assertEquals((Long) a1id, mDbHelper.getAccountIdOrNull(AccountWithDataSet.LOCAL)); 101 102 // Remove all accounts. 103 mDbHelper.getWritableDatabase().execSQL("delete from " + Tables.ACCOUNTS); 104 105 assertNull(mDbHelper.getAccountIdOrNull(AccountWithDataSet.LOCAL)); 106 assertNull(mDbHelper.getAccountIdOrNull(a1)); 107 assertNull(mDbHelper.getAccountIdOrNull(a2)); 108 assertNull(mDbHelper.getAccountIdOrNull(a3)); 109 assertNull(mDbHelper.getAccountIdOrNull(a4)); 110 assertNull(mDbHelper.getAccountIdOrNull(a5)); 111 112 // Logically same as a5, but physically different object. 113 final AccountWithDataSet a5b = new AccountWithDataSet("a", "b", "c"); 114 // a5 and a5b should have the same ID. 115 assertEquals( 116 mDbHelper.getOrCreateAccountIdInTransaction(a5), 117 mDbHelper.getOrCreateAccountIdInTransaction(a5b)); 118 } 119 120 /** 121 * Test for {@link ContactsDatabaseHelper#queryIdWithOneArg} and 122 * {@link ContactsDatabaseHelper#insertWithOneArgAndReturnId}. 123 */ testQueryIdWithOneArg_insertWithOneArgAndReturnId()124 public void testQueryIdWithOneArg_insertWithOneArgAndReturnId() { 125 final String query = 126 "SELECT " + MimetypesColumns._ID + 127 " FROM " + Tables.MIMETYPES + 128 " WHERE " + MimetypesColumns.MIMETYPE + "=?"; 129 130 final String insert = 131 "INSERT INTO " + Tables.MIMETYPES + "(" 132 + MimetypesColumns.MIMETYPE + 133 ") VALUES (?)"; 134 135 // First, the table is empty. 136 assertEquals(-1, ContactsDatabaseHelper.queryIdWithOneArg(mDb, query, "value1")); 137 assertEquals(-1, ContactsDatabaseHelper.queryIdWithOneArg(mDb, query, "value2")); 138 139 // Insert one value. 140 final long id1 = ContactsDatabaseHelper.insertWithOneArgAndReturnId(mDb, insert, "value1"); 141 MoreAsserts.assertNotEqual(-1, id1); 142 143 assertEquals(id1, ContactsDatabaseHelper.queryIdWithOneArg(mDb, query, "value1")); 144 assertEquals(-1, ContactsDatabaseHelper.queryIdWithOneArg(mDb, query, "value2")); 145 146 147 // Insert one value. 148 final long id2 = ContactsDatabaseHelper.insertWithOneArgAndReturnId(mDb, insert, "value2"); 149 MoreAsserts.assertNotEqual(-1, id2); 150 151 assertEquals(id1, ContactsDatabaseHelper.queryIdWithOneArg(mDb, query, "value1")); 152 assertEquals(id2, ContactsDatabaseHelper.queryIdWithOneArg(mDb, query, "value2")); 153 154 // Insert the same value and cause a conflict. 155 assertEquals(-1, ContactsDatabaseHelper.insertWithOneArgAndReturnId(mDb, insert, "value2")); 156 } 157 158 /** 159 * Test for {@link ContactsDatabaseHelper#getPackageId(String)} and 160 * {@link ContactsDatabaseHelper#getMimeTypeId(String)}. 161 * 162 * We test them at the same time here, to make sure they're not mixing up the caches. 163 */ testGetPackageId_getMimeTypeId()164 public void testGetPackageId_getMimeTypeId() { 165 166 // Test for getPackageId. 167 final long packageId1 = mDbHelper.getPackageId("value1"); 168 final long packageId2 = mDbHelper.getPackageId("value2"); 169 final long packageId3 = mDbHelper.getPackageId("value3"); 170 171 // Make sure they're all different. 172 final HashSet<Long> set = new HashSet<>(); 173 set.add(packageId1); 174 set.add(packageId2); 175 set.add(packageId3); 176 177 assertEquals(3, set.size()); 178 179 // Test for getMimeTypeId. 180 final long mimetypeId1 = mDbHelper.getMimeTypeId("value1"); 181 final long mimetypeId2 = mDbHelper.getMimeTypeId("value2"); 182 final long mimetypeId3 = mDbHelper.getMimeTypeId("value3"); 183 184 // Make sure they're all different. 185 set.clear(); 186 set.add(mimetypeId1); 187 set.add(mimetypeId2); 188 set.add(mimetypeId3); 189 190 assertEquals(3, set.size()); 191 192 // Call with the same values and make sure they return the cached value. 193 final long packageId1b = mDbHelper.getPackageId("value1"); 194 final long mimetypeId1b = mDbHelper.getMimeTypeId("value1"); 195 196 assertEquals(packageId1, packageId1b); 197 assertEquals(mimetypeId1, mimetypeId1b); 198 199 // Make sure the caches are also updated. 200 assertEquals(packageId2, (long) mDbHelper.mPackageCache.get("value2")); 201 assertEquals(mimetypeId2, (long) mDbHelper.mMimetypeCache.get("value2")); 202 203 // Clear the cache, but they should still return the values, selecting from the database. 204 mDbHelper.mPackageCache.clear(); 205 mDbHelper.mMimetypeCache.clear(); 206 assertEquals(packageId1, mDbHelper.getPackageId("value1")); 207 assertEquals(mimetypeId1, mDbHelper.getMimeTypeId("value1")); 208 209 // Empty the table 210 mDb.execSQL("DELETE FROM " + Tables.MIMETYPES); 211 212 // We should still have the cached value. 213 assertEquals(mimetypeId1, mDbHelper.getMimeTypeId("value1")); 214 } 215 216 /** 217 * Try to cause conflicts in getMimeTypeId() by calling it from multiple threads with 218 * the current time as the argument and make sure it won't crash. 219 * 220 * We don't know from the test if there have actually been conflits, but if you look at 221 * logcat you'll see a lot of conflict warnings. 222 */ 223 @LargeTest testGetMimeTypeId_conflict()224 public void testGetMimeTypeId_conflict() { 225 226 final int NUM_THREADS = 4; 227 final int DURATION_SECONDS = 5; 228 229 final long finishTime = System.currentTimeMillis() + DURATION_SECONDS * 1000; 230 231 final Runnable r = new Runnable() { 232 @Override 233 public void run() { 234 for (;;) { 235 final long now = System.currentTimeMillis(); 236 if (now >= finishTime) { 237 return; 238 } 239 assertTrue(mDbHelper.getMimeTypeId(String.valueOf(now)) > 0); 240 } 241 } 242 }; 243 final Thread[] threads = new Thread[NUM_THREADS]; 244 for (int i = 0; i < threads.length; i++) { 245 threads[i] = new Thread(r); 246 threads[i].setDaemon(true); 247 } 248 for (int i = 0; i < threads.length; i++) { 249 threads[i].start(); 250 } 251 for (int i = 0; i < threads.length; i++) { 252 try { 253 threads[i].join(); 254 } catch (InterruptedException ignore) { 255 } 256 } 257 } 258 testUpgradeHashId()259 public void testUpgradeHashId() { 260 // Create an account. 261 final long accountId = mDbHelper.getOrCreateAccountIdInTransaction( 262 AccountWithDataSet.LOCAL); 263 // Create a raw contact. 264 ContentValues rawContactValues = new ContentValues(); 265 rawContactValues.put(ContactsDatabaseHelper.RawContactsColumns.ACCOUNT_ID, accountId); 266 final long rawContactId = mDb.insert(Tables.RAW_CONTACTS,null, rawContactValues); 267 assertTrue(rawContactId > 0); 268 // Create data for the raw contact Id. 269 final StringBuilder data1 = new StringBuilder(); 270 for (int i = 0; i < 2048; i++) { 271 data1.append("L"); 272 } 273 final String dataString = data1.toString(); 274 final String hashId = mDbHelper.generateHashId(dataString, null); 275 final int mimeType = 1; 276 final ContentValues values = new ContentValues(); 277 values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId); 278 values.put(ContactsDatabaseHelper.DataColumns.MIMETYPE_ID, mimeType); 279 values.put(ContactsContract.Data.DATA1, dataString); 280 for (int i = 0; i < 2048; i++) { 281 assertTrue(mDb.insert(Tables.DATA, null, values) > 0); 282 } 283 mDbHelper.upgradeToVersion1101(mDb); 284 final Cursor c = mDb.query(Tables.DATA, new String[]{ContactsContract.Data.HASH_ID}, 285 null, null, null, null, null); 286 try { 287 assertEquals(2048, c.getCount()); 288 while (c.moveToNext()) { 289 final String expectedHashId = c.getString(0); 290 assertEquals(expectedHashId, hashId); 291 } 292 } finally { 293 c.close(); 294 } 295 } 296 testUpgradeHashIdForPhoto()297 public void testUpgradeHashIdForPhoto() { 298 // Create an account. 299 final long accountId = mDbHelper.getOrCreateAccountIdInTransaction( 300 AccountWithDataSet.LOCAL); 301 // Create a raw contact. 302 ContentValues rawContactValues = new ContentValues(); 303 rawContactValues.put(ContactsDatabaseHelper.RawContactsColumns.ACCOUNT_ID, accountId); 304 final long rawContactId = mDb.insert(Tables.RAW_CONTACTS,null, rawContactValues); 305 assertTrue(rawContactId > 0); 306 307 // Create data for the raw contact Id. 308 final long mimeType = mDbHelper.getMimeTypeId( 309 ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE); 310 final String photoHashId = mDbHelper.getPhotoHashId(); 311 final ContentValues values = new ContentValues(); 312 values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId); 313 values.put(ContactsDatabaseHelper.DataColumns.MIMETYPE_ID, mimeType); 314 for (int i = 0; i < 2048; i++) { 315 assertTrue(mDb.insert(Tables.DATA, null, values) > 0); 316 } 317 mDbHelper.upgradeToVersion1110(mDb); 318 final Cursor c = mDb.query(Tables.DATA, new String[]{ContactsContract.Data.HASH_ID}, 319 null, null, null, null, null); 320 try { 321 assertEquals(2048, c.getCount()); 322 while (c.moveToNext()) { 323 final String actualHashId = c.getString(0); 324 assertEquals(photoHashId, actualHashId); 325 } 326 } finally { 327 c.close(); 328 } 329 } 330 testUpgradeToVersion111_SetPrimaryPhonebookBucketToNumberBucket()331 public void testUpgradeToVersion111_SetPrimaryPhonebookBucketToNumberBucket() { 332 // Zero primary phone book bucket and null primary sort key 333 final ContentValues contentValues = new ContentValues(); 334 contentValues.put(RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY, 0); 335 mDb.insert(Tables.RAW_CONTACTS, null, contentValues); 336 337 mDbHelper.upgradeToVersion1111(mDb); 338 339 // Assert that the primary phone book bucket/label has been set to the number bucket/label 340 final ContactLocaleUtils localeUtils = ContactLocaleUtils.getInstance(); 341 final int numberBucket = localeUtils.getNumberBucketIndex(); 342 final String numberLabel = localeUtils.getBucketLabel(numberBucket); 343 assertUpgradeToVersion1111(numberBucket, numberLabel, 344 RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY, 345 RawContactsColumns.PHONEBOOK_LABEL_PRIMARY); 346 } 347 testUpgradeToVersion111_SetAltPhonebookBucketToNumberBucket()348 public void testUpgradeToVersion111_SetAltPhonebookBucketToNumberBucket() { 349 // Zero alt phone book bucket and null alt sort key 350 final ContentValues contentValues = new ContentValues(); 351 contentValues.put(RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE, 0); 352 mDb.insert(Tables.RAW_CONTACTS, null, contentValues); 353 354 mDbHelper.upgradeToVersion1111(mDb); 355 356 // Assert that the alt phone book bucket/label has been set to the number bucket/label 357 final ContactLocaleUtils localeUtils = ContactLocaleUtils.getInstance(); 358 final int numberBucket = localeUtils.getNumberBucketIndex(); 359 final String numberLabel = localeUtils.getBucketLabel(numberBucket); 360 assertUpgradeToVersion1111(numberBucket, numberLabel, 361 RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE, 362 RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE); 363 } 364 testUpgradeToVersion111_NonZeroPrimaryPhonebookBucket()365 public void testUpgradeToVersion111_NonZeroPrimaryPhonebookBucket() { 366 // Non-zero primary phone book bucket 367 final int primaryBucket = 1; 368 final ContentValues contentValues = new ContentValues(); 369 contentValues.put(RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY, primaryBucket); 370 mDb.insert(Tables.RAW_CONTACTS, null, contentValues); 371 372 mDbHelper.upgradeToVersion1111(mDb); 373 374 // Assert that the primary phone book bucket/label is unchanged 375 assertUpgradeToVersion1111(primaryBucket, null, RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY, 376 RawContactsColumns.PHONEBOOK_LABEL_PRIMARY); 377 } 378 testUpgradeToVersion111_NonNullPrimarySortKey()379 public void testUpgradeToVersion111_NonNullPrimarySortKey() { 380 // Non-null primary sort key 381 final ContentValues contentValues = new ContentValues(); 382 contentValues.put(RawContacts.SORT_KEY_PRIMARY, "sort_key_primary"); 383 mDb.insert(Tables.RAW_CONTACTS, null, contentValues); 384 385 mDbHelper.upgradeToVersion1111(mDb); 386 387 // Assert that the primary phone book bucket/label is unchanged 388 assertUpgradeToVersion1111(0, null, RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY, 389 RawContactsColumns.PHONEBOOK_LABEL_PRIMARY); 390 } 391 testUpgradeToVersion111_NonZeroAltPhonebookBucket()392 public void testUpgradeToVersion111_NonZeroAltPhonebookBucket() { 393 // Non-zero alt phone book bucket 394 final int altBucket = 1; 395 final ContentValues contentValues = new ContentValues(); 396 contentValues.put(RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE, altBucket); 397 mDb.insert(Tables.RAW_CONTACTS, null, contentValues); 398 399 mDbHelper.upgradeToVersion1111(mDb); 400 401 // Assert that the alt phone book bucket/label is unchanged 402 assertUpgradeToVersion1111(altBucket, null, RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE, 403 RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE); 404 } 405 testUpgradeToVersion111_NonNullAltSortKeyToNumber()406 public void testUpgradeToVersion111_NonNullAltSortKeyToNumber() { 407 // Non-null alt sort key 408 final ContentValues contentValues = new ContentValues(); 409 contentValues.put(RawContacts.SORT_KEY_ALTERNATIVE, "sort_key_alt"); 410 mDb.insert(Tables.RAW_CONTACTS, null, contentValues); 411 412 mDbHelper.upgradeToVersion1111(mDb); 413 414 // Assert that the alt phone book bucket/label is unchanged 415 assertUpgradeToVersion1111(0, null, RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE, 416 RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE); 417 } 418 assertUpgradeToVersion1111(int expectedBucket, String expectedLabel, String bucketColumn, String labelColumn)419 private void assertUpgradeToVersion1111(int expectedBucket, String expectedLabel, 420 String bucketColumn, String labelColumn) { 421 final Cursor cursor = mDb.query(Tables.RAW_CONTACTS, 422 new String[]{bucketColumn, labelColumn}, null, null, null, null, null); 423 try { 424 assertEquals(1, cursor.getCount()); 425 assertTrue(cursor.moveToNext()); 426 assertEquals(expectedBucket, cursor.getInt(0)); 427 assertEquals(expectedLabel, cursor.getString(1)); 428 } finally { 429 cursor.close(); 430 } 431 } 432 } 433