1/* 2 * Copyright (C) 2009 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 17package com.android.providers.contacts; 18 19import com.google.android.collect.Lists; 20 21import android.accounts.Account; 22import android.content.ContentProviderOperation; 23import android.content.ContentUris; 24import android.content.ContentValues; 25import android.content.OperationApplicationException; 26import android.database.Cursor; 27import android.net.Uri; 28import android.os.RemoteException; 29import android.provider.ContactsContract; 30import android.provider.ContactsContract.AggregationExceptions; 31import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 32import android.provider.ContactsContract.Contacts; 33import android.provider.ContactsContract.Groups; 34import android.provider.ContactsContract.Settings; 35import android.test.suitebuilder.annotation.LargeTest; 36import android.test.suitebuilder.annotation.MediumTest; 37 38import java.util.ArrayList; 39 40/** 41 * Unit tests for {@link Groups} and {@link GroupMembership}. 42 * 43 * Run the test like this: 44 * <code> 45 * adb shell am instrument -e class com.android.providers.contacts.GroupsTest -w \ 46 * com.android.providers.contacts.tests/android.test.InstrumentationTestRunner 47 * </code> 48 */ 49public class GroupsTest extends BaseContactsProvider2Test { 50 51 private static final String GROUP_GREY = "Grey"; 52 private static final String GROUP_RED = "Red"; 53 private static final String GROUP_GREEN = "Green"; 54 private static final String GROUP_BLUE = "Blue"; 55 56 private static final String PERSON_ALPHA = "Alpha"; 57 private static final String PERSON_BRAVO = "Bravo"; 58 private static final String PERSON_CHARLIE = "Charlie"; 59 private static final String PERSON_DELTA = "Delta"; 60 61 private static final String PHONE_ALPHA = "555-1111"; 62 private static final String PHONE_BRAVO_1 = "555-2222"; 63 private static final String PHONE_BRAVO_2 = "555-3333"; 64 private static final String PHONE_CHARLIE_1 = "555-4444"; 65 private static final String PHONE_CHARLIE_2 = "555-5555"; 66 67 @LargeTest 68 public void testGroupSummary() { 69 70 // Clear any existing data before starting 71 // TODO make the provider wipe data automatically 72 ((SynchronousContactsProvider2)mActor.provider).wipeData(); 73 74 // Create a handful of groups 75 long groupGrey = mActor.createGroup(GROUP_GREY); 76 long groupRed = mActor.createGroup(GROUP_RED); 77 long groupGreen = mActor.createGroup(GROUP_GREEN); 78 long groupBlue = mActor.createGroup(GROUP_BLUE); 79 80 // Create a handful of contacts 81 long contactAlpha = mActor.createRawContact(PERSON_ALPHA); 82 long contactBravo = mActor.createRawContact(PERSON_BRAVO); 83 long contactCharlie = mActor.createRawContact(PERSON_CHARLIE); 84 long contactCharlieDupe = mActor.createRawContact(PERSON_CHARLIE); 85 setAggregationException( 86 AggregationExceptions.TYPE_KEEP_TOGETHER, contactCharlie, contactCharlieDupe); 87 long contactDelta = mActor.createRawContact(PERSON_DELTA); 88 89 assertAggregated(contactCharlie, contactCharlieDupe); 90 91 // Add phone numbers to specific contacts 92 mActor.createPhone(contactAlpha, PHONE_ALPHA); 93 mActor.createPhone(contactBravo, PHONE_BRAVO_1); 94 mActor.createPhone(contactBravo, PHONE_BRAVO_2); 95 mActor.createPhone(contactCharlie, PHONE_CHARLIE_1); 96 mActor.createPhone(contactCharlieDupe, PHONE_CHARLIE_2); 97 98 // Add contacts to various mixture of groups. Grey will have all 99 // contacts, Red only with phone numbers, Green with no phones, and Blue 100 // with no contacts at all. 101 mActor.createGroupMembership(contactAlpha, groupGrey); 102 mActor.createGroupMembership(contactBravo, groupGrey); 103 mActor.createGroupMembership(contactCharlie, groupGrey); 104 mActor.createGroupMembership(contactDelta, groupGrey); 105 106 mActor.createGroupMembership(contactAlpha, groupRed); 107 mActor.createGroupMembership(contactBravo, groupRed); 108 mActor.createGroupMembership(contactCharlie, groupRed); 109 110 mActor.createGroupMembership(contactDelta, groupGreen); 111 112 // Walk across groups summary cursor and verify returned counts. 113 final Cursor cursor = mActor.resolver.query(Groups.CONTENT_SUMMARY_URI, 114 Projections.PROJ_SUMMARY, null, null, null); 115 116 // Require that each group has a summary row 117 assertTrue("Didn't return summary for all groups", (cursor.getCount() == 4)); 118 119 while (cursor.moveToNext()) { 120 final long groupId = cursor.getLong(Projections.COL_ID); 121 final int summaryCount = cursor.getInt(Projections.COL_SUMMARY_COUNT); 122 final int summaryWithPhones = cursor.getInt(Projections.COL_SUMMARY_WITH_PHONES); 123 124 if (groupId == groupGrey) { 125 // Grey should have four aggregates, three with phones. 126 assertEquals("Incorrect Grey count", 4, summaryCount); 127 assertEquals("Incorrect Grey with phones count", 3, summaryWithPhones); 128 } else if (groupId == groupRed) { 129 // Red should have 3 aggregates, all with phones. 130 assertEquals("Incorrect Red count", 3, summaryCount); 131 assertEquals("Incorrect Red with phones count", 3, summaryWithPhones); 132 } else if (groupId == groupGreen) { 133 // Green should have 1 aggregate, none with phones. 134 assertEquals("Incorrect Green count", 1, summaryCount); 135 assertEquals("Incorrect Green with phones count", 0, summaryWithPhones); 136 } else if (groupId == groupBlue) { 137 // Blue should have no contacts. 138 assertEquals("Incorrect Blue count", 0, summaryCount); 139 assertEquals("Incorrect Blue with phones count", 0, summaryWithPhones); 140 } else { 141 fail("Unrecognized group in summary cursor"); 142 } 143 } 144 cursor.close(); 145 } 146 147 @MediumTest 148 public void testGroupDirtySetOnChange() { 149 Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI, 150 createGroup(mAccount, "gsid1", "title1")); 151 assertDirty(uri, true); 152 clearDirty(uri); 153 assertDirty(uri, false); 154 } 155 156 @MediumTest 157 public void testMarkAsDirtyParameter() { 158 Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI, 159 createGroup(mAccount, "gsid1", "title1")); 160 clearDirty(uri); 161 Uri updateUri = setCallerIsSyncAdapter(uri, mAccount); 162 163 ContentValues values = new ContentValues(); 164 values.put(Groups.NOTES, "New notes"); 165 mResolver.update(updateUri, values, null, null); 166 assertDirty(uri, false); 167 } 168 169 @MediumTest 170 public void testGroupDirtyClearedWhenSetExplicitly() { 171 Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI, 172 createGroup(mAccount, "gsid1", "title1")); 173 assertDirty(uri, true); 174 175 ContentValues values = new ContentValues(); 176 values.put(Groups.DIRTY, 0); 177 values.put(Groups.NOTES, "other notes"); 178 assertEquals(1, mResolver.update(uri, values, null, null)); 179 180 assertDirty(uri, false); 181 } 182 183 @MediumTest 184 public void testGroupDeletion1() { 185 long groupId = createGroup(mAccount, "g1", "gt1"); 186 Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId); 187 188 assertEquals(1, getCount(uri, null, null)); 189 mResolver.delete(uri, null, null); 190 assertEquals(1, getCount(uri, null, null)); 191 assertStoredValue(uri, Groups.DELETED, "1"); 192 193 Uri permanentDeletionUri = setCallerIsSyncAdapter(uri, mAccount); 194 mResolver.delete(permanentDeletionUri, null, null); 195 assertEquals(0, getCount(uri, null, null)); 196 } 197 198 @MediumTest 199 public void testGroupDeletion2() { 200 long groupId = createGroup(mAccount, "g1", "gt1"); 201 Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId); 202 203 assertEquals(1, getCount(uri, null, null)); 204 Uri permanentDeletionUri = setCallerIsSyncAdapter(uri, mAccount); 205 mResolver.delete(permanentDeletionUri, null, null); 206 assertEquals(0, getCount(uri, null, null)); 207 } 208 209 @MediumTest 210 public void testGroupVersionUpdates() { 211 Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI, 212 createGroup(mAccount, "gsid1", "title1")); 213 long version = getVersion(uri); 214 ContentValues values = new ContentValues(); 215 values.put(Groups.TITLE, "title2"); 216 mResolver.update(uri, values, null, null); 217 assertEquals(version + 1, getVersion(uri)); 218 } 219 220 private interface Projections { 221 public static final String[] PROJ_SUMMARY = new String[] { 222 Groups._ID, 223 Groups.SUMMARY_COUNT, 224 Groups.SUMMARY_WITH_PHONES, 225 }; 226 227 public static final int COL_ID = 0; 228 public static final int COL_SUMMARY_COUNT = 1; 229 public static final int COL_SUMMARY_WITH_PHONES = 2; 230 } 231 232 private static final Account sTestAccount = new Account("user@example.com", "com.example"); 233 private static final Account sSecondAccount = new Account("other@example.net", "net.example"); 234 private static final String GROUP_ID = "testgroup"; 235 236 public void assertRawContactVisible(long rawContactId, boolean expected) { 237 final long contactId = this.queryContactId(rawContactId); 238 assertContactVisible(contactId, expected); 239 } 240 241 public void assertContactVisible(long contactId, boolean expected) { 242 final Cursor cursor = mResolver.query(Contacts.CONTENT_URI, new String[] { 243 Contacts.IN_VISIBLE_GROUP 244 }, Contacts._ID + "=" + contactId, null, null); 245 assertTrue("Contact not found", cursor.moveToFirst()); 246 final boolean actual = (cursor.getInt(0) != 0); 247 cursor.close(); 248 assertEquals("Unexpected visibility", expected, actual); 249 } 250 251 public ContentProviderOperation buildVisibleAssert(long contactId, boolean visible) { 252 return ContentProviderOperation.newAssertQuery(Contacts.CONTENT_URI).withSelection( 253 Contacts._ID + "=" + contactId + " AND " + Contacts.IN_VISIBLE_GROUP + "=" 254 + (visible ? 1 : 0), null).withExpectedCount(1).build(); 255 } 256 257 @LargeTest 258 public void testDelayVisibleTransaction() throws RemoteException, OperationApplicationException { 259 final ContentValues values = new ContentValues(); 260 261 final long groupId = this.createGroup(sTestAccount, GROUP_ID, GROUP_ID, 1); 262 final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, groupId); 263 264 // Create contact with specific membership 265 final long rawContactId = this.createRawContact(sTestAccount); 266 final long contactId = this.queryContactId(rawContactId); 267 final Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); 268 269 this.insertGroupMembership(rawContactId, groupId); 270 271 final ArrayList<ContentProviderOperation> oper = Lists.newArrayList(); 272 273 // Update visibility inside a transaction and assert that inside the 274 // transaction it hasn't been updated yet. 275 oper.add(buildVisibleAssert(contactId, true)); 276 oper.add(ContentProviderOperation.newUpdate(groupUri).withValue(Groups.GROUP_VISIBLE, 0) 277 .build()); 278 oper.add(buildVisibleAssert(contactId, true)); 279 mResolver.applyBatch(ContactsContract.AUTHORITY, oper); 280 281 // After previous transaction finished, visibility should be updated 282 oper.clear(); 283 oper.add(buildVisibleAssert(contactId, false)); 284 mResolver.applyBatch(ContactsContract.AUTHORITY, oper); 285 } 286 287 public void testLocalSingleVisible() { 288 final long rawContactId = this.createRawContact(); 289 290 // Single, local contacts should always be visible 291 assertRawContactVisible(rawContactId, true); 292 } 293 294 public void testLocalMixedVisible() { 295 // Aggregate, when mixed with local, should become visible 296 final long rawContactId1 = this.createRawContact(); 297 final long rawContactId2 = this.createRawContact(sTestAccount); 298 299 final long groupId = this.createGroup(sTestAccount, GROUP_ID, GROUP_ID, 0); 300 this.insertGroupMembership(rawContactId2, groupId); 301 302 // Make sure they are still apart 303 assertNotAggregated(rawContactId1, rawContactId2); 304 assertRawContactVisible(rawContactId1, true); 305 assertRawContactVisible(rawContactId2, false); 306 307 // Force together and see what happens 308 final ContentValues values = new ContentValues(); 309 values.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER); 310 values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1); 311 values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2); 312 mResolver.update(AggregationExceptions.CONTENT_URI, values, null, null); 313 314 assertRawContactVisible(rawContactId1, true); 315 assertRawContactVisible(rawContactId2, true); 316 } 317 318 public void testUngroupedVisible() { 319 final long rawContactId = this.createRawContact(sTestAccount); 320 321 final ContentValues values = new ContentValues(); 322 values.put(Settings.ACCOUNT_NAME, sTestAccount.name); 323 values.put(Settings.ACCOUNT_TYPE, sTestAccount.type); 324 values.put(Settings.UNGROUPED_VISIBLE, 0); 325 mResolver.insert(Settings.CONTENT_URI, values); 326 327 assertRawContactVisible(rawContactId, false); 328 329 values.clear(); 330 values.put(Settings.UNGROUPED_VISIBLE, 1); 331 mResolver.update(Settings.CONTENT_URI, values, Settings.ACCOUNT_NAME + "=? AND " 332 + Settings.ACCOUNT_TYPE + "=?", new String[] { 333 sTestAccount.name, sTestAccount.type 334 }); 335 336 assertRawContactVisible(rawContactId, true); 337 } 338 339 public void testMultipleSourcesVisible() { 340 final long rawContactId1 = this.createRawContact(sTestAccount); 341 final long rawContactId2 = this.createRawContact(sSecondAccount); 342 343 final long groupId = this.createGroup(sTestAccount, GROUP_ID, GROUP_ID, 0); 344 this.insertGroupMembership(rawContactId1, groupId); 345 346 // Make sure still invisible 347 assertRawContactVisible(rawContactId1, false); 348 assertRawContactVisible(rawContactId2, false); 349 350 // Make group visible 351 final ContentValues values = new ContentValues(); 352 values.put(Groups.GROUP_VISIBLE, 1); 353 mResolver.update(Groups.CONTENT_URI, values, Groups._ID + "=" + groupId, null); 354 355 assertRawContactVisible(rawContactId1, true); 356 assertRawContactVisible(rawContactId2, false); 357 358 // Force them together 359 values.clear(); 360 values.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER); 361 values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1); 362 values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2); 363 mResolver.update(AggregationExceptions.CONTENT_URI, values, null, null); 364 365 assertRawContactVisible(rawContactId1, true); 366 assertRawContactVisible(rawContactId2, true); 367 368 // Make group invisible 369 values.clear(); 370 values.put(Groups.GROUP_VISIBLE, 0); 371 mResolver.update(Groups.CONTENT_URI, values, Groups._ID + "=" + groupId, null); 372 373 assertRawContactVisible(rawContactId1, false); 374 assertRawContactVisible(rawContactId2, false); 375 376 // Turn on ungrouped for first 377 values.clear(); 378 values.put(Settings.ACCOUNT_NAME, sTestAccount.name); 379 values.put(Settings.ACCOUNT_TYPE, sTestAccount.type); 380 values.put(Settings.UNGROUPED_VISIBLE, 1); 381 mResolver.insert(Settings.CONTENT_URI, values); 382 383 assertRawContactVisible(rawContactId1, false); 384 assertRawContactVisible(rawContactId2, false); 385 386 // Turn on ungrouped for second account 387 values.clear(); 388 values.put(Settings.ACCOUNT_NAME, sSecondAccount.name); 389 values.put(Settings.ACCOUNT_TYPE, sSecondAccount.type); 390 values.put(Settings.UNGROUPED_VISIBLE, 1); 391 mResolver.insert(Settings.CONTENT_URI, values); 392 393 assertRawContactVisible(rawContactId1, true); 394 assertRawContactVisible(rawContactId2, true); 395 } 396} 397