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