1 /* 2 * Copyright (C) 2016 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.contacts; 18 19 import android.content.ContentProviderOperation; 20 import android.content.ContentResolver; 21 import android.content.ContentUris; 22 import android.content.ContentValues; 23 import android.content.Context; 24 import android.content.OperationApplicationException; 25 import android.database.Cursor; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.os.RemoteException; 29 import android.provider.ContactsContract; 30 import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 31 import android.provider.ContactsContract.Data; 32 import android.test.InstrumentationTestCase; 33 import android.test.suitebuilder.annotation.MediumTest; 34 35 import com.android.contacts.model.account.AccountWithDataSet; 36 37 import java.util.ArrayList; 38 import java.util.List; 39 40 /** 41 * Tests of GroupsDaoImpl that perform DB operations directly against CP2 42 */ 43 @MediumTest 44 public class GroupsDaoIntegrationTests extends InstrumentationTestCase { 45 46 private ContentResolver mResolver; 47 private List<Uri> mTestRecords; 48 49 @Override setUp()50 protected void setUp() throws Exception { 51 super.setUp(); 52 53 mTestRecords = new ArrayList<>(); 54 mResolver = getContext().getContentResolver(); 55 } 56 57 @Override tearDown()58 protected void tearDown() throws Exception { 59 super.tearDown(); 60 61 // Cleanup anything leftover by the tests. 62 cleanupTestRecords(); 63 mTestRecords.clear(); 64 } 65 test_createGroup_createsGroupWithCorrectTitle()66 public void test_createGroup_createsGroupWithCorrectTitle() throws Exception { 67 final ContactSaveService.GroupsDao sut = createDao(); 68 final Uri uri = sut.create("Test Create Group", getLocalAccount()); 69 70 assertNotNull(uri); 71 assertGroupHasTitle(uri, "Test Create Group"); 72 } 73 test_deleteEmptyGroup_marksRowDeleted()74 public void test_deleteEmptyGroup_marksRowDeleted() throws Exception { 75 final ContactSaveService.GroupsDao sut = createDao(); 76 final Uri uri = sut.create("Test Delete Group", getLocalAccount()); 77 78 assertEquals(1, sut.delete(uri)); 79 80 final Cursor cursor = mResolver.query(uri, null, null, null, null, null); 81 try { 82 cursor.moveToFirst(); 83 assertEquals(1, cursor.getInt(cursor.getColumnIndexOrThrow( 84 ContactsContract.Groups.DELETED))); 85 } finally { 86 cursor.close(); 87 } 88 } 89 test_undoDeleteEmptyGroup_createsGroupWithMatchingTitle()90 public void test_undoDeleteEmptyGroup_createsGroupWithMatchingTitle() throws Exception { 91 final ContactSaveService.GroupsDao sut = createDao(); 92 final Uri uri = sut.create("Test Undo Delete Empty Group", getLocalAccount()); 93 94 final Bundle undoData = sut.captureDeletionUndoData(uri); 95 96 assertEquals(1, sut.delete(uri)); 97 98 final Uri groupUri = sut.undoDeletion(undoData); 99 100 assertGroupHasTitle(groupUri, "Test Undo Delete Empty Group"); 101 } 102 test_deleteNonEmptyGroup_removesGroupAndMembers()103 public void test_deleteNonEmptyGroup_removesGroupAndMembers() throws Exception { 104 final ContactSaveService.GroupsDao sut = createDao(); 105 final Uri groupUri = sut.create("Test delete non-empty group", getLocalAccount()); 106 107 final long groupId = ContentUris.parseId(groupUri); 108 addMemberToGroup(ContentUris.parseId(createRawContact()), groupId); 109 addMemberToGroup(ContentUris.parseId(createRawContact()), groupId); 110 111 assertEquals(1, sut.delete(groupUri)); 112 113 final Cursor cursor = mResolver.query(Data.CONTENT_URI, null, 114 Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?", 115 new String[] { GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId) }, 116 null, null); 117 118 try { 119 cursor.moveToFirst(); 120 // This is more of a characterization test since our code isn't manually deleting 121 // the membership rows just the group but this still helps document the expected 122 // behavior. 123 assertEquals(0, cursor.getCount()); 124 } finally { 125 cursor.close(); 126 } 127 } 128 test_undoDeleteNonEmptyGroup_restoresGroupAndMembers()129 public void test_undoDeleteNonEmptyGroup_restoresGroupAndMembers() throws Exception { 130 final ContactSaveService.GroupsDao sut = createDao(); 131 final Uri groupUri = sut.create("Test undo delete non-empty group", getLocalAccount()); 132 133 final long groupId = ContentUris.parseId(groupUri); 134 addMemberToGroup(ContentUris.parseId(createRawContact()), groupId); 135 addMemberToGroup(ContentUris.parseId(createRawContact()), groupId); 136 137 final Bundle undoData = sut.captureDeletionUndoData(groupUri); 138 139 sut.delete(groupUri); 140 141 final Uri recreatedGroup = sut.undoDeletion(undoData); 142 143 final long newGroupId = ContentUris.parseId(recreatedGroup); 144 145 final Cursor cursor = mResolver.query(Data.CONTENT_URI, null, 146 Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?", 147 new String[] { GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(newGroupId) }, 148 null, null); 149 150 try { 151 assertEquals(2, cursor.getCount()); 152 } finally { 153 cursor.close(); 154 } 155 } 156 test_captureUndoDataForDeletedGroup_returnsEmptyBundle()157 public void test_captureUndoDataForDeletedGroup_returnsEmptyBundle() { 158 final ContactSaveService.GroupsDao sut = createDao(); 159 160 final Uri uri = sut.create("a deleted group", getLocalAccount()); 161 sut.delete(uri); 162 163 final Bundle undoData = sut.captureDeletionUndoData(uri); 164 165 assertTrue(undoData.isEmpty()); 166 } 167 test_captureUndoDataForNonExistentGroup_returnsEmptyBundle()168 public void test_captureUndoDataForNonExistentGroup_returnsEmptyBundle() { 169 final ContactSaveService.GroupsDao sut = createDao(); 170 171 // This test could potentially be flaky if this ID exists for some reason. 10 is subtracted 172 // to reduce the likelihood of this happening; some other test may use Integer.MAX_VALUE 173 // or nearby values to cover some special case or boundary condition. 174 final long nonExistentId = Integer.MAX_VALUE - 10; 175 176 final Bundle undoData = sut.captureDeletionUndoData(ContentUris 177 .withAppendedId(ContactsContract.Groups.CONTENT_URI, nonExistentId)); 178 179 assertTrue(undoData.isEmpty()); 180 } 181 test_undoWithEmptyBundle_doesNothing()182 public void test_undoWithEmptyBundle_doesNothing() { 183 final ContactSaveService.GroupsDao sut = createDao(); 184 185 final Uri uri = sut.undoDeletion(new Bundle()); 186 187 assertNull(uri); 188 } 189 test_undoDeleteEmptyGroupWithMissingMembersKey_shouldRecreateGroup()190 public void test_undoDeleteEmptyGroupWithMissingMembersKey_shouldRecreateGroup() { 191 final ContactSaveService.GroupsDao sut = createDao(); 192 final Uri groupUri = sut.create("Test undo delete null memberIds", getLocalAccount()); 193 194 final Bundle undoData = sut.captureDeletionUndoData(groupUri); 195 undoData.remove(ContactSaveService.GroupsDaoImpl.KEY_GROUP_MEMBERS); 196 sut.delete(groupUri); 197 198 sut.undoDeletion(undoData); 199 200 assertGroupWithTitleExists("Test undo delete null memberIds"); 201 } 202 assertGroupHasTitle(Uri groupUri, String title)203 private void assertGroupHasTitle(Uri groupUri, String title) { 204 final Cursor cursor = mResolver.query(groupUri, 205 new String[] { ContactsContract.Groups.TITLE }, 206 ContactsContract.Groups.DELETED + "=?", 207 new String[] { "0" }, null, null); 208 try { 209 assertTrue("Group does not have title \"" + title + "\"", 210 cursor.getCount() == 1 && cursor.moveToFirst() && 211 title.equals(cursor.getString(0))); 212 } finally { 213 cursor.close(); 214 } 215 } 216 assertGroupWithTitleExists(String title)217 private void assertGroupWithTitleExists(String title) { 218 final Cursor cursor = mResolver.query(ContactsContract.Groups.CONTENT_URI, null, 219 ContactsContract.Groups.TITLE + "=? AND " + 220 ContactsContract.Groups.DELETED + "=?", 221 new String[] { title, "0" }, null, null); 222 try { 223 assertTrue("No group exists with title \"" + title + "\"", cursor.getCount() > 0); 224 } finally { 225 cursor.close(); 226 } 227 } 228 createDao()229 public ContactSaveService.GroupsDao createDao() { 230 return new GroupsDaoWrapper(new ContactSaveService.GroupsDaoImpl(getContext())); 231 } 232 createRawContact()233 private Uri createRawContact() { 234 final ContentValues values = new ContentValues(); 235 values.putNull(ContactsContract.RawContacts.ACCOUNT_NAME); 236 values.putNull(ContactsContract.RawContacts.ACCOUNT_TYPE); 237 final Uri result = mResolver.insert(ContactsContract.RawContacts.CONTENT_URI, values); 238 mTestRecords.add(result); 239 return result; 240 } 241 addMemberToGroup(long rawContactId, long groupId)242 private Uri addMemberToGroup(long rawContactId, long groupId) { 243 final ContentValues values = new ContentValues(); 244 values.put(Data.RAW_CONTACT_ID, rawContactId); 245 values.put(Data.MIMETYPE, 246 GroupMembership.CONTENT_ITEM_TYPE); 247 values.put(GroupMembership.GROUP_ROW_ID, groupId); 248 249 // Dont' need to add to testRecords because it will be cleaned up when parent raw_contact 250 // is deleted. 251 return mResolver.insert(Data.CONTENT_URI, values); 252 } 253 getContext()254 private Context getContext() { 255 return getInstrumentation().getTargetContext(); 256 } 257 getLocalAccount()258 private AccountWithDataSet getLocalAccount() { 259 return new AccountWithDataSet(null, null, null); 260 } 261 cleanupTestRecords()262 private void cleanupTestRecords() throws RemoteException, OperationApplicationException { 263 final ArrayList<ContentProviderOperation> ops = new ArrayList<>(); 264 for (Uri uri : mTestRecords) { 265 if (uri == null) continue; 266 ops.add(ContentProviderOperation 267 .newDelete(uri.buildUpon() 268 .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true") 269 .build()) 270 .build()); 271 } 272 mResolver.applyBatch(ContactsContract.AUTHORITY, ops); 273 } 274 275 private class GroupsDaoWrapper implements ContactSaveService.GroupsDao { 276 private final ContactSaveService.GroupsDao mDelegate; 277 GroupsDaoWrapper(ContactSaveService.GroupsDao delegate)278 public GroupsDaoWrapper(ContactSaveService.GroupsDao delegate) { 279 mDelegate = delegate; 280 } 281 282 @Override create(String title, AccountWithDataSet account)283 public Uri create(String title, AccountWithDataSet account) { 284 final Uri result = mDelegate.create(title, account); 285 mTestRecords.add(result); 286 return result; 287 } 288 289 @Override delete(Uri groupUri)290 public int delete(Uri groupUri) { 291 return mDelegate.delete(groupUri); 292 } 293 294 @Override captureDeletionUndoData(Uri groupUri)295 public Bundle captureDeletionUndoData(Uri groupUri) { 296 return mDelegate.captureDeletionUndoData(groupUri); 297 } 298 299 @Override undoDeletion(Bundle undoData)300 public Uri undoDeletion(Bundle undoData) { 301 final Uri result = mDelegate.undoDeletion(undoData); 302 mTestRecords.add(result); 303 return result; 304 } 305 } 306 } 307