├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── androidcontacts ├── bintray.gradle ├── build.gradle └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── tomash │ │ └── androidcontacts │ │ ├── BaseTest.kt │ │ ├── tests │ │ ├── BlockedTests.kt │ │ ├── DeleteContactTest.kt │ │ ├── EqualsTests.kt │ │ ├── GetContactsTests.kt │ │ └── InsertTests.java │ │ └── utils │ │ ├── ACResultTestRule.kt │ │ ├── RandomString.kt │ │ ├── TelecomTestUtils.java │ │ └── TestUtils.kt │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── tomash │ └── androidcontacts │ └── contactgetter │ ├── acresult │ └── ACResult.kt │ ├── entity │ ├── Address.java │ ├── ContactData.java │ ├── Email.java │ ├── Group.java │ ├── IMAddress.java │ ├── NameData.java │ ├── Organization.java │ ├── PhoneNumber.java │ ├── Relation.java │ └── SpecialDate.java │ ├── interfaces │ ├── BaseFilter.java │ ├── FieldFilter.java │ ├── Filterable.java │ ├── ListFilter.java │ └── WithLabel.java │ ├── main │ ├── ContactDataFactory.java │ ├── FieldType.java │ ├── Sorting.java │ ├── blocked │ │ └── BlockedContactsManager.kt │ ├── contactsDeleter │ │ └── ContactsDeleter.kt │ ├── contactsGetter │ │ ├── ContactsGetter.java │ │ └── ContactsGetterBuilder.java │ ├── contactsSaver │ │ ├── ContactsSaver.java │ │ └── ContactsSaverBuilder.java │ └── contactsUpdater │ │ └── ContactsUpdater.java │ └── utils │ └── FilterUtils.java ├── build.gradle ├── deps.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── testapp ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src └── main ├── AndroidManifest.xml ├── java └── com │ └── tomash │ └── testapp │ ├── DeleteExample.kt │ └── JavaDeleteExample.java └── res ├── drawable-v24 └── ic_launcher_foreground.xml ├── drawable └── ic_launcher_background.xml ├── mipmap-xxxhdpi ├── ic_launcher.png └── ic_launcher_round.png └── values ├── colors.xml ├── strings.xml └── styles.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Android template 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # IntelliJ 38 | *.iml 39 | .idea/* 40 | 41 | # Keystore files 42 | # Uncomment the following line if you do not want to check your keystore files in. 43 | #*.jks 44 | 45 | # External native build folder generated in Android Studio 2.2 and later 46 | .externalNativeBuild 47 | 48 | # Google Services (e.g. APIs or Firebase) 49 | google-services.json 50 | 51 | # Freeline 52 | freeline.py 53 | freeline/ 54 | freeline_project_description.json 55 | 56 | # fastlane 57 | fastlane/report.xml 58 | fastlane/Preview.html 59 | fastlane/screenshots 60 | fastlane/test_output 61 | fastlane/readme.md 62 | 63 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | ### 1.14.2 Apr 17, 2021 5 | > * Added possibility to set and get default phone number 6 | 7 | ### 1.14.1 Apr 14, 2021 8 | > * Added possibility to set string as sort order to support other sorting orders like update timestamp 9 | 10 | ### 1.14.0 May 25, 2020 11 | > * Added possibility to delete contact 12 | 13 | ### 1.13.0 May 11, 2020 14 | > * Added possibility to get blocked numbers and block and unblock them using BlockedContactsManager 15 | 16 | ### 1.12.5 Apr 8, 2020 17 | > * Fixed possibility of NPE during equals of contact data 18 | 19 | ### 1.12.4 Feb 19, 2020 20 | > * Added isFavorite to detect and save favorite contacts 21 | 22 | ### 1.12.3 Feb 4, 2020 23 | > * Removed unnecessary properties from manifest 24 | 25 | ### 1.12.2 Sep 25, 2019 26 | > * Fixed issue with phone formatting during searching by number 27 | 28 | ### 1.12.1 Mar 5, 2019 29 | > * Fixed regressing with filters 30 | > * Fixed regression with sorting 31 | 32 | ### 1.12.0 Feb 25, 2019 33 | > * Added account type and account name in ContactData to support saving and getting account names for contacts 34 | 35 | ### 1.11.0 Jan 7, 2019 36 | > * Added possibility to save bitmap or uri with photos of contact 37 | > * Minor refactor 38 | 39 | ### 1.10.0 Mar 6, 2017 40 | > * Added contacts saver 41 | > * Fixed bugs for Android api<18 42 | > * Covered contacts saver and getter with tests 43 | > * Reworked WithLabel objects creation 44 | 45 | ### 1.0.6 Mar 3, 2017 46 | > * Improved contacts getter performance 47 | 48 | ### 1.0.5 Mar 1, 2017 49 | > * Added support of custom Contact objects 50 | > * Added lookup key 51 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 blainepwnz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AndroidContacts 2 | =================== 3 | ---------------------------------- 4 | [![GitHub license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/blainepwnz/AndroidContacts/blob/master/LICENSE.txt) 5 | [![](https://jitpack.io/v/blainepwnz/AndroidContacts.svg)](https://jitpack.io/#blainepwnz/AndroidContacts) 6 | 7 | 8 | Gradle Dependency 9 | --------------------------- 10 | **Step 1.** Add it in your root build.gradle at the end of repositories: 11 | ``` 12 | allprojects { 13 | repositories { 14 | ... 15 | maven { url 'https://jitpack.io' } 16 | } 17 | } 18 | ``` 19 | **Step 2.** Add the dependency 20 | ``` 21 | dependencies { 22 | implementation("com.github.blainepwnz:AndroidContacts:1.14.0") 23 | } 24 | ``` 25 | 26 | 27 | Basic Usage 28 | ------------------ 29 | 30 | >The minimum API level supported by this library is API 15. 31 | >Library is not handling 23+ API permission. 32 | 33 | Need to add permission to read contacts in manifest. 34 | ``` 35 | 36 | 37 | ``` 38 | 39 | ##### Basic usage 40 | 1. Get all contacts from android device 41 | 2. Get specific data from contacts 42 | 3. Querying inside contacts 43 | 4. Save new contacts 44 | 5. Delete contacts 45 | 5. Managing blocked contacts 46 | 47 | #### Type of fields you can get 48 | 49 | * Phones 50 | * Emails 51 | * Addresses 52 | * Groups 53 | * Websites 54 | * Nickname 55 | * Note 56 | * SIP 57 | * Name 58 | * Events 59 | * Relations 60 | * Photo 61 | * Instant Messenger Addresses 62 | * Lookup key 63 | * Is favorite 64 | 65 | Easiest way to get all data by one time: 66 | ``` 67 | new ContactsGetterBuilder(ctx) 68 | .allFields() 69 | .buildList(); 70 | ``` 71 | 72 | Querying 73 | ------------------ 74 | 75 | Example: query that gets all contacts with photo and containing sequence "abc" in name. 76 | ``` 77 | new ContactsGetterBuilder(ctx) 78 | .onlyWithPhotos() 79 | .addField(FieldType.EMAILS, FieldType.ADDRESS) 80 | .withNameLike("abc"); 81 | ``` 82 | 83 | Saving new contacts 84 | ------------------- 85 | 86 | ``` 87 | ContactData data = ContactDataFactory.createEmpty(); 88 | ...Fill it with data 89 | // id of newly created contact 90 | int id = new ContactsSaverBuilder(context) 91 | .saveContact(data); 92 | ``` 93 | 94 | Deleting contacts 95 | ------------------- 96 | 97 | ``` 98 | val contactData = /* get contact data from somewhere */ 99 | val deleter = ContactsDeleter(context) 100 | //usual delete with no need of callbacks 101 | deleter.deleteContact(contactData) 102 | //full range of callbacks, implement any you need 103 | deleter.deleteContact(contactData) { 104 | onCompleted { } 105 | onFailure { } 106 | onResult { } 107 | doFinally { } 108 | } 109 | ``` 110 | 111 | #### Additional info 112 | [More documentation](https://github.com/blainepwnz/AndroidContacts/wiki/Documentation) 113 | 114 | #### Changelog 115 | [More documentation](https://github.com/blainepwnz/AndroidContacts/blob/master/CHANGELOG.md) 116 | -------------------------------------------------------------------------------- /androidcontacts/bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | apply from: '../deps.gradle' 3 | 4 | def mSiteUrl = 'https://github.com/blainepwnz/AndroidContacts' 5 | def mGitUrl = 'https://github.com/blainepwnz/AndroidContacts.git' 6 | 7 | tasks.withType(Javadoc) { 8 | options.addStringOption('Xdoclint:none', '-quiet') 9 | options.addStringOption('encoding', 'UTF-8') 10 | options.addStringOption('charSet', 'UTF-8') 11 | } 12 | android { 13 | compileSdkVersion versions.compileSdkVersion 14 | } 15 | ext { 16 | bintrayRepo = 'maven' 17 | bintrayName = versions.artifact 18 | 19 | publishedGroupId = 'com.tomash' 20 | libraryName = versions.artifact 21 | artifact = versions.artifact 22 | 23 | libraryDescription = 'Android library for contact aggregation' 24 | 25 | // Your github repo link 26 | siteUrl = mSiteUrl 27 | gitUrl = mGitUrl 28 | githubRepository = 'blainepwnz/AndroidContacts' 29 | 30 | libraryVersion = versions.publishVersion 31 | 32 | developerId = 'blainepwnz' 33 | developerName = 'Andrew Tomash' 34 | developerEmail = 'andrew.tomash@titanium-soft.com' 35 | licenseName = 'MIT' 36 | licenseUrl = 'https://opensource.org/licenses/MIT' 37 | allLicenses = ["MIT"] 38 | } 39 | bintray { 40 | pkg { 41 | userOrg = "blainepwnz" 42 | } 43 | } 44 | apply from: 'https://raw.githubusercontent.com/nisrulz/JCenter/master/installv1.gradle' 45 | apply from: 'https://raw.githubusercontent.com/nisrulz/JCenter/master/bintrayv1.gradle' 46 | 47 | -------------------------------------------------------------------------------- /androidcontacts/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | apply from: 'bintray.gradle' 7 | apply from: '../deps.gradle' 8 | 9 | android { 10 | compileSdkVersion versions.compileSdkVersion 11 | buildToolsVersion "30.0.2" 12 | 13 | defaultConfig { 14 | minSdkVersion 15 15 | targetSdkVersion 30 16 | versionCode versions.buildCode 17 | versionName versions.publishVersion 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | androidExtensions { 22 | experimental = true 23 | } 24 | 25 | sourceSets { 26 | main.java.srcDirs += 'src/main/kotlin' 27 | main.kotlin.srcDirs = ['src/main/kotlin', 'src/main/java'] 28 | test.kotlin.srcDirs = ['src/test/kotlin', 'src/test/java'] 29 | test.java.srcDirs = ['src/test/kotlin', 'src/test/java'] 30 | } 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | tasks.withType(Javadoc).all { 38 | enabled = false 39 | } 40 | 41 | } 42 | 43 | dependencies { 44 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 45 | androidTestImplementation 'androidx.test:runner:1.3.0' 46 | androidTestImplementation 'androidx.test:rules:1.3.0' 47 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 48 | androidTestImplementation "org.assertj:assertj-core:3.11.1" 49 | 50 | implementation 'com.android.support:support-annotations:28.0.0' 51 | 52 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin_version" 53 | testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$versions.kotlin_version" 54 | testImplementation "org.jetbrains.kotlin:kotlin-test:$versions.kotlin_version" 55 | } 56 | 57 | -------------------------------------------------------------------------------- /androidcontacts/src/androidTest/java/com/tomash/androidcontacts/BaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts 2 | 3 | import android.Manifest 4 | import androidx.test.core.app.ApplicationProvider 5 | import androidx.test.ext.junit.runners.AndroidJUnit4 6 | import androidx.test.filters.LargeTest 7 | import androidx.test.rule.GrantPermissionRule 8 | import com.tomash.androidcontacts.utils.clearAllContacts 9 | import com.tomash.androidcontacts.utils.context 10 | import org.junit.Before 11 | import org.junit.Rule 12 | import org.junit.runner.RunWith 13 | 14 | @LargeTest 15 | @RunWith(AndroidJUnit4::class) 16 | open class BaseTest { 17 | 18 | @get:Rule 19 | var mRuntimePermissionRule = 20 | GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS) 21 | 22 | @Before 23 | @Throws(Exception::class) 24 | fun setUp() { 25 | context = ApplicationProvider.getApplicationContext() 26 | clearAllContacts() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /androidcontacts/src/androidTest/java/com/tomash/androidcontacts/tests/BlockedTests.kt: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.tests 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import com.tomash.androidcontacts.BaseTest 5 | import com.tomash.androidcontacts.contactgetter.main.blocked.BlockedContactsManager 6 | import com.tomash.androidcontacts.utils.TelecomTestUtils 7 | import com.tomash.androidcontacts.utils.context 8 | import com.tomash.androidcontacts.utils.randomString 9 | import org.junit.Assert 10 | import org.junit.Assert.fail 11 | import org.junit.Test 12 | 13 | class BlockedTests : BaseTest() { 14 | 15 | @Test 16 | fun correctlyBlocksContact() { 17 | test { 18 | val number = randomString() 19 | block(number) { 20 | fail("Should not fail") 21 | } 22 | val blockedNumbers = getBlockedNumbers { 23 | fail("Should not fail") 24 | } 25 | Assert.assertEquals(1, blockedNumbers.size) 26 | Assert.assertTrue(blockedNumbers.first() == number) 27 | } 28 | } 29 | 30 | @Test 31 | fun throwsExceptionIfNotDefaultDialer() { 32 | test(setAsDefaultDialer = false) { 33 | var exception: Exception? = null 34 | getBlockedNumbers { 35 | exception = it 36 | } 37 | Assert.assertNotNull(exception) 38 | Assert.assertEquals(exception?.javaClass, SecurityException::class.java) 39 | } 40 | } 41 | 42 | @Test 43 | fun correctlyUnblocksContact() { 44 | correctlyBlocksContact() 45 | test(clearAllBlockedContacts = false) { 46 | val availableNumber = getBlockedNumbers().first() 47 | unblock(availableNumber) 48 | val allBlockedNumbers = getBlockedNumbers() 49 | Assert.assertTrue(allBlockedNumbers.isEmpty()) 50 | } 51 | } 52 | 53 | @Test 54 | fun doesNothingIfTriesToUnblockUnknownNumber() { 55 | test { 56 | val number = randomString() 57 | block(number) 58 | unblock(number + "123") { 59 | fail("Should not fail") 60 | } 61 | Assert.assertEquals(1, getBlockedNumbers().size) 62 | Assert.assertTrue(getBlockedNumbers().first() == number) 63 | } 64 | } 65 | 66 | @Test 67 | fun doesNotAddSameNumberTwoTimes() { 68 | test { 69 | val number = randomString() 70 | block(number) 71 | block(number) 72 | Assert.assertEquals(1, getBlockedNumbers().size) 73 | } 74 | } 75 | 76 | private fun test(setAsDefaultDialer: Boolean = true, 77 | clearAllBlockedContacts: Boolean = true, 78 | testFunc: BlockedContactsManager.() -> Unit) { 79 | val blockedContactsManager = BlockedContactsManager(context) 80 | if (setAsDefaultDialer) { 81 | TelecomTestUtils.setDefaultDialer(InstrumentationRegistry.getInstrumentation(), context.packageName) 82 | if (clearAllBlockedContacts) { 83 | blockedContactsManager.getBlockedNumbers().forEach { 84 | blockedContactsManager.unblock(it) 85 | } 86 | } 87 | } else { 88 | TelecomTestUtils.setDefaultDialer(InstrumentationRegistry.getInstrumentation(), context.packageName + "1234") 89 | } 90 | blockedContactsManager.testFunc() 91 | } 92 | } -------------------------------------------------------------------------------- /androidcontacts/src/androidTest/java/com/tomash/androidcontacts/tests/DeleteContactTest.kt: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.tests 2 | 3 | import com.tomash.androidcontacts.BaseTest 4 | import com.tomash.androidcontacts.contactgetter.main.contactsDeleter.ContactsDeleter 5 | import com.tomash.androidcontacts.contactgetter.main.contactsDeleter.UnsuccessfulDeleteException 6 | import com.tomash.androidcontacts.utils.ACResultTestRule 7 | import com.tomash.androidcontacts.utils.context 8 | import com.tomash.androidcontacts.utils.createRandomContactData 9 | import com.tomash.androidcontacts.utils.getAllContacts 10 | import com.tomash.androidcontacts.utils.mockContactDataList 11 | import com.tomash.androidcontacts.utils.saveAll 12 | import com.tomash.androidcontacts.utils.wrap 13 | import org.junit.Assert 14 | import org.junit.Rule 15 | import org.junit.Test 16 | 17 | class DeleteContactTest : BaseTest() { 18 | 19 | @get:Rule 20 | var callbackRule = ACResultTestRule() 21 | 22 | @Test 23 | fun deletesCorrectlyByOne() { 24 | mockContactDataList().saveAll() 25 | val deleter = ContactsDeleter(context) 26 | getAllContacts().forEach { 27 | deleter.deleteContact(it, wrap( 28 | callbackRule.shouldComplete(), 29 | callbackRule.shouldGetResult(it)) 30 | ) 31 | } 32 | Assert.assertTrue(getAllContacts().isEmpty()) 33 | } 34 | 35 | @Test 36 | fun deletesCorrectlyAll() { 37 | mockContactDataList().saveAll() 38 | val deleter = ContactsDeleter(context) 39 | val allContacts = getAllContacts() 40 | deleter.deleteContacts(allContacts, wrap( 41 | callbackRule.shouldComplete(), 42 | callbackRule.shouldGetResult(allContacts) 43 | )) 44 | Assert.assertTrue(getAllContacts().isEmpty()) 45 | } 46 | 47 | @Test 48 | fun oneContactDeleteFails() { 49 | val data = createRandomContactData() 50 | val deleter = ContactsDeleter(context) 51 | deleter.deleteContact(data, callbackRule.shouldFailWithError(UnsuccessfulDeleteException())) 52 | } 53 | 54 | @Test 55 | fun multipleContactDeleteFails() { 56 | val first = createRandomContactData() 57 | val second = createRandomContactData() 58 | val deleter = ContactsDeleter(context) 59 | deleter.deleteContacts(listOf(first, second), 60 | callbackRule.shouldFailWithError(mapOf( 61 | first to UnsuccessfulDeleteException(), 62 | second to UnsuccessfulDeleteException()) 63 | )) 64 | } 65 | 66 | @Test 67 | fun mixOfSuccessAndFailWorks() { 68 | mockContactDataList().saveAll() 69 | val toBeRemoved = getAllContacts().first() 70 | val data = createRandomContactData() 71 | val deleter = ContactsDeleter(context) 72 | deleter.deleteContacts(listOf(toBeRemoved, data), 73 | wrap( 74 | callbackRule.shouldFailWithError(mapOf(data to UnsuccessfulDeleteException())), 75 | callbackRule.shouldGetResult(listOf(toBeRemoved)) 76 | ) 77 | ) 78 | } 79 | 80 | @Test 81 | fun finishesWithCompleteOnEmptyList() { 82 | val deleter = ContactsDeleter(context) 83 | deleter.deleteContacts(listOf(), 84 | wrap( 85 | callbackRule.shouldHaveNoResults(), 86 | callbackRule.shouldComplete() 87 | ) 88 | ) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /androidcontacts/src/androidTest/java/com/tomash/androidcontacts/tests/EqualsTests.kt: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.tests 2 | 3 | import com.tomash.androidcontacts.BaseTest 4 | import com.tomash.androidcontacts.contactgetter.entity.ContactData 5 | import org.junit.Assert 6 | import org.junit.Test 7 | 8 | class EqualsTests : BaseTest() { 9 | 10 | @Test 11 | @Throws(Exception::class) 12 | fun equalsNotCrashing() { 13 | Assert.assertEquals(nullContactData(), nullContactData()) 14 | } 15 | 16 | private fun nullContactData() = object : ContactData() {}.apply { 17 | accountName = null 18 | accountType = null 19 | addressesList = null 20 | compositeName = null 21 | emailList = null 22 | groupList = null 23 | imAddressesList = null 24 | nameData = null 25 | nickName = null 26 | organization = null 27 | note = null 28 | websitesList = null 29 | updatedPhotoUri = null 30 | photoUri = null 31 | lookupKey = null 32 | updatedBitmap = null 33 | specialDatesList = null 34 | sipAddress = null 35 | relationsList = null 36 | phoneList = null 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /androidcontacts/src/androidTest/java/com/tomash/androidcontacts/tests/GetContactsTests.kt: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.tests 2 | 3 | import com.tomash.androidcontacts.BaseTest 4 | import com.tomash.androidcontacts.contactgetter.entity.ContactData 5 | import com.tomash.androidcontacts.contactgetter.entity.PhoneNumber 6 | import com.tomash.androidcontacts.contactgetter.main.Sorting 7 | import com.tomash.androidcontacts.contactgetter.main.contactsGetter.ContactsGetterBuilder 8 | import com.tomash.androidcontacts.contactgetter.main.contactsSaver.ContactsSaverBuilder 9 | import com.tomash.androidcontacts.utils.assertContacts 10 | import com.tomash.androidcontacts.utils.context 11 | import com.tomash.androidcontacts.utils.createRandomContactData 12 | import org.junit.Assert 13 | import org.junit.Test 14 | import java.util.ArrayList 15 | 16 | class GetContactsTests : BaseTest() { 17 | 18 | private fun createRandomList(listAction: (List) -> Unit): List { 19 | val savedData = generateListOfRandomContacts() 20 | listAction(savedData) 21 | ContactsSaverBuilder(context) 22 | .saveContactsList(savedData) 23 | return savedData 24 | } 25 | 26 | private fun createRandomContact(contactAction: (ContactData) -> Unit): ContactData { 27 | val savedData = createRandomContactData() 28 | contactAction(savedData) 29 | ContactsSaverBuilder(context) 30 | .saveContact(savedData) 31 | return savedData 32 | } 33 | 34 | private fun getList(builderFunc: ContactsGetterBuilder.() -> ContactsGetterBuilder): List { 35 | return builderFunc(ContactsGetterBuilder(context) 36 | .allFields()) 37 | .buildList() 38 | } 39 | 40 | @Test 41 | fun correctlyGetsValidContacts() { 42 | val savedData = createRandomList {} 43 | savedData.forEachIndexed { index, _ -> 44 | val randomContact = savedData[index] 45 | val savedList = getList { withName(randomContact.compositeName) } 46 | Assert.assertEquals(1, savedList.size) 47 | assertContacts(randomContact, savedList.first(), index.inc()) 48 | } 49 | } 50 | 51 | @Test 52 | fun correctlyGetsPrimaryPhone() { 53 | val phone = "12345" 54 | createRandomContact { 55 | val phoneNumber = it.phoneList.first() 56 | phoneNumber.mainData = phone 57 | phoneNumber.isPrimary = true 58 | 59 | } 60 | val saved = getList { this } 61 | Assert.assertEquals(1, saved.first().phoneList.filter { it.isPrimary }.size) 62 | Assert.assertTrue(saved.first().phoneList.first { it.mainData == phone }.isPrimary) 63 | } 64 | 65 | @Test 66 | @Throws(Exception::class) 67 | fun queryingByPhoneWorksCorrectly() { 68 | val savedData = createRandomList { 69 | it.take(it.size / 2).forEach { it.phoneList.clear() } 70 | } 71 | val savedList = getList { onlyWithPhones() } 72 | Assert.assertEquals((savedData.size / 2).toLong(), savedList.size.toLong()) 73 | } 74 | 75 | @Test 76 | @Throws(Exception::class) 77 | fun queryingByPhotoWorksCorrectly() { 78 | val savedData = createRandomList { 79 | it.take(it.size / 2).forEach { it.updatedBitmap = null } 80 | } 81 | val savedList = getList { onlyWithPhotos() } 82 | Assert.assertEquals((savedData.size / 2).toLong(), savedList.size.toLong()) 83 | } 84 | 85 | @Test 86 | @Throws(Exception::class) 87 | fun comboQueryIsWorking() { 88 | val savedData = createRandomList { 89 | it.take(it.size / 2).forEach { 90 | it.updatedBitmap = null 91 | it.phoneList.clear() 92 | } 93 | } 94 | val savedList = getList { onlyWithPhotos().onlyWithPhones() } 95 | Assert.assertEquals((savedData.size / 2).toLong(), savedList.size.toLong()) 96 | } 97 | 98 | @Test 99 | @Throws(Exception::class) 100 | fun ascendingNameSortingWorksAsExpected() { 101 | val savedData = createRandomList {} 102 | val savedList = getList { setSortOrder(Sorting.BY_DISPLAY_NAME_ASC) } 103 | savedData.sortedBy { it.compositeName }.forEachIndexed { index, contactData -> 104 | Assert.assertTrue(savedList[index].compositeName == contactData.compositeName) 105 | } 106 | } 107 | 108 | @Test 109 | fun withPhoneLikeFiltersPhones() { 110 | //create list with 50 contacts and save them 111 | createRandomList { 112 | //removing all random phones 113 | //adding numbers from example 114 | it.forEach { it.phoneList.clear() } 115 | it[0].phoneList.add(PhoneNumber("+631230001234", "1234")) 116 | it[1].phoneList.add(PhoneNumber("+1 123 000 1234", "1234")) 117 | it[2].phoneList.add(PhoneNumber("01230001234", "1234")) 118 | //adding one number that should be filtered 119 | it[3].phoneList.add(PhoneNumber("001234", "1234")) 120 | } 121 | //getting contacts from android 122 | val savedList = getList { withPhoneLike("1230001234") } 123 | //asserting that all of them were added 124 | Assert.assertEquals(3, savedList.size) 125 | } 126 | 127 | @Test 128 | fun withPhoneFiltersPhones() { 129 | //create list with 50 contacts and save them 130 | createRandomList { 131 | //removing all random phones 132 | //adding numbers from example 133 | it.forEach { it.phoneList.clear() } 134 | it.first().phoneList.add(PhoneNumber("+631230001234", "1234")) 135 | it.first().phoneList.add(PhoneNumber("1230001234", "1234")) 136 | it.first().phoneList.add(PhoneNumber("123000", "1234")) 137 | } 138 | //getting contacts from android 139 | //asserting that all of them were added 140 | val first = getList { withPhone("1230001234") } 141 | val second = getList { withPhone("+631230001234") } 142 | val third = getList { withPhone("123000") } 143 | //asserting that all lists are the same and containing same numbers 144 | Assert.assertEquals(1, first.size) 145 | Assert.assertTrue(first == second && second == third) 146 | } 147 | 148 | @Test 149 | @Throws(Exception::class) 150 | fun descendingNameSortingWorksAsExpected() { 151 | val savedData = createRandomList {} 152 | val savedList = getList { setSortOrder(Sorting.BY_DISPLAY_NAME_DESC) } 153 | savedData.sortedByDescending { it.compositeName }.forEachIndexed { index, contactData -> 154 | Assert.assertTrue(savedList[index].compositeName == contactData.compositeName) 155 | } 156 | } 157 | 158 | @Test 159 | fun ascendingByIsSortingWorking() { 160 | createRandomList {} 161 | getList { setSortOrder(Sorting.BY_ID_ASC) }.zipWithNext().forEach { 162 | Assert.assertTrue(it.first.contactId < it.second.contactId) 163 | } 164 | } 165 | 166 | @Test 167 | fun descendingByIdSortingIsWorking() { 168 | createRandomList {} 169 | getList { setSortOrder(Sorting.BY_ID_DESC) }.zipWithNext().forEach { 170 | Assert.assertTrue(it.first.contactId > it.second.contactId) 171 | } 172 | } 173 | 174 | @Throws(Exception::class) 175 | private fun generateListOfRandomContacts(): List { 176 | val dataList = ArrayList() 177 | for (i in 1..50) { 178 | dataList.add(createRandomContactData(i)) 179 | } 180 | return dataList 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /androidcontacts/src/androidTest/java/com/tomash/androidcontacts/tests/InsertTests.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.tests; 2 | 3 | import com.tomash.androidcontacts.BaseTest; 4 | import com.tomash.androidcontacts.contactgetter.entity.ContactData; 5 | import com.tomash.androidcontacts.contactgetter.main.ContactDataFactory; 6 | import com.tomash.androidcontacts.contactgetter.main.contactsGetter.ContactsGetterBuilder; 7 | import com.tomash.androidcontacts.contactgetter.main.contactsSaver.ContactsSaverBuilder; 8 | import com.tomash.androidcontacts.utils.RandomStringKt; 9 | import com.tomash.androidcontacts.utils.TestUtilsKt; 10 | import org.junit.Assert; 11 | import org.junit.Test; 12 | 13 | import java.util.ArrayList; 14 | import java.util.HashSet; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | public class InsertTests extends BaseTest { 19 | 20 | @Test 21 | public void correctlyInsertsDataList() throws Exception { 22 | int listSize = 100; 23 | List dataList = getMockContactDataList(); 24 | int[] ids = new ContactsSaverBuilder(TestUtilsKt.context) 25 | .saveContactsList(dataList); 26 | Assert.assertEquals(100, ids.length); 27 | //asserting data inside 28 | Set namesSet = new HashSet<>(); 29 | Set idsSet = new HashSet<>(); 30 | for (int i = 0; i < listSize; i++) { 31 | idsSet.add(ids[i]); 32 | namesSet.add(dataList.get(i).getCompositeName()); 33 | } 34 | List fromDb = new ContactsGetterBuilder(TestUtilsKt.context) 35 | .allFields() 36 | .buildList(); 37 | for (ContactData contactData : fromDb) { 38 | Assert.assertTrue(namesSet.contains(contactData.getCompositeName())); 39 | Assert.assertTrue(idsSet.contains(contactData.getContactId())); 40 | } 41 | } 42 | 43 | @Test 44 | public void correctlyInsertsOneData() throws Exception { 45 | int bitmapSize = 10; 46 | ContactData contactData = TestUtilsKt.createRandomContactData(bitmapSize); 47 | int id = saveToDb(contactData); 48 | Assert.assertTrue(id > 0); 49 | ContactData savedContact = getFromDbById(id); 50 | TestUtilsKt.assertContacts(contactData, savedContact, bitmapSize); 51 | } 52 | 53 | @Test 54 | public void photoUriIsChangedCorrectly() throws Exception { 55 | int firstBitmapSize = 10; 56 | int secondBitmapSize = 20; 57 | //creating and saving first contactData to db 58 | ContactData contactData1 = TestUtilsKt.createRandomContactData(firstBitmapSize); 59 | int firstId = saveToDb(contactData1); 60 | contactData1 = getFromDbById(firstId); 61 | //creating second contact data and assigning uri of bitmap from first to it 62 | ContactData contactData2 = TestUtilsKt.createRandomContactData(secondBitmapSize); 63 | contactData2.setUpdatedPhotoUri(contactData1.getPhotoUri()); 64 | int secondId = saveToDb(contactData2); 65 | contactData2 = getFromDbById(secondId); 66 | Assert.assertEquals(TestUtilsKt.getBitmapFromContactData(contactData2).getHeight(), firstBitmapSize); 67 | } 68 | 69 | private ContactData getFromDbById(int id) { 70 | return new ContactsGetterBuilder(TestUtilsKt.context) 71 | .allFields() 72 | .getById(id); 73 | } 74 | 75 | private int saveToDb(ContactData contactData) { 76 | return new ContactsSaverBuilder(TestUtilsKt.context) 77 | .saveContact(contactData); 78 | } 79 | 80 | private List getMockContactDataList() { 81 | List contactDatas = new ArrayList<>(); 82 | for (int i = 0; i < 100; i++) { 83 | ContactData data = ContactDataFactory.createEmpty(); 84 | data.setCompositeName(RandomStringKt.randomString()); 85 | contactDatas.add(data); 86 | } 87 | return contactDatas; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /androidcontacts/src/androidTest/java/com/tomash/androidcontacts/utils/ACResultTestRule.kt: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.utils 2 | 3 | import com.tomash.androidcontacts.contactgetter.acresult.ACResult 4 | import junit.framework.TestCase.fail 5 | import org.junit.Assert 6 | import org.junit.rules.TestRule 7 | import org.junit.runner.Description 8 | import org.junit.runners.model.Statement 9 | 10 | class ACResultTestRule : TestRule { 11 | 12 | private var uncalledCallbacks = 0 13 | 14 | override fun apply(base: Statement?, description: Description?) = object : Statement() { 15 | override fun evaluate() { 16 | base?.evaluate() 17 | if (uncalledCallbacks != 0) { 18 | fail("Remaining null callbacks = $uncalledCallbacks") 19 | } 20 | } 21 | } 22 | 23 | fun shouldComplete(): ACResult<*, *>.() -> Unit { 24 | uncalledCallbacks = uncalledCallbacks.inc() 25 | return { 26 | onCompleted { 27 | uncalledCallbacks = uncalledCallbacks.dec() 28 | } 29 | onFailure { 30 | Assert.fail("Should not call on failure") 31 | } 32 | } 33 | } 34 | 35 | fun shouldFail(): ACResult<*, *>.() -> Unit { 36 | uncalledCallbacks = uncalledCallbacks.inc() 37 | return { 38 | onFailure { 39 | uncalledCallbacks = uncalledCallbacks.dec() 40 | } 41 | onCompleted { 42 | Assert.fail("Should not call on completed") 43 | } 44 | } 45 | } 46 | 47 | fun shouldFailWithError(expectedError: E): ACResult.() -> Unit = 48 | wrap( 49 | { 50 | onFailure { 51 | expectedError isEqual it 52 | } 53 | }, 54 | shouldFail() 55 | ) 56 | 57 | fun shouldGetResult(expectedResult: R): ACResult.() -> Unit = { 58 | onResult { 59 | expectedResult isEqual it 60 | } 61 | } 62 | 63 | fun shouldHaveNoResults(): ACResult<*, *>.() -> Unit = { 64 | onResult { 65 | Assert.fail("Should not call onResult, value = $it") 66 | } 67 | } 68 | } 69 | 70 | fun wrap(vararg functions: ACResult.() -> Unit): ACResult.() -> Unit = { 71 | functions.forEach { it() } 72 | } 73 | -------------------------------------------------------------------------------- /androidcontacts/src/androidTest/java/com/tomash/androidcontacts/utils/RandomString.kt: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.utils 2 | 3 | import java.security.SecureRandom 4 | 5 | private const val AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 6 | private val rnd = SecureRandom() 7 | 8 | @JvmOverloads 9 | fun randomString(len: Int = 7): String { 10 | val sb = StringBuilder(len) 11 | for (i in 0 until len) sb.append(AB[rnd.nextInt(AB.length)]) 12 | return sb.toString() 13 | } 14 | -------------------------------------------------------------------------------- /androidcontacts/src/androidTest/java/com/tomash/androidcontacts/utils/TelecomTestUtils.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.utils; 2 | 3 | /* 4 | * Copyright (C) 2015 The Android Open Source Project 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * Copied from http://android.macpod.net/raw/android-6.0.1_r77/cts/tests/tests/telecom/src/android/telecom/cts/TestUtils.java 19 | */ 20 | 21 | import android.app.Instrumentation; 22 | import android.content.ComponentName; 23 | import android.content.Context; 24 | import android.content.pm.PackageManager; 25 | import android.os.Build; 26 | import android.os.ParcelFileDescriptor; 27 | import android.telecom.PhoneAccountHandle; 28 | 29 | import java.io.BufferedReader; 30 | import java.io.FileInputStream; 31 | import java.io.InputStream; 32 | import java.io.InputStreamReader; 33 | import java.nio.charset.StandardCharsets; 34 | 35 | public class TelecomTestUtils { 36 | static final String TAG = "TelecomCTSTests"; 37 | static final boolean HAS_TELECOM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; 38 | static final long WAIT_FOR_STATE_CHANGE_TIMEOUT_MS = 10000; 39 | 40 | // Non-final to allow modification by tests not in this package (e.g. permission-related 41 | // tests in the Telecom2 test package. 42 | public static String PACKAGE = "com.android.cts.telecom"; 43 | public static final String COMPONENT = "android.telecom.cts.CtsConnectionService"; 44 | public static final String REMOTE_COMPONENT = "android.telecom.cts.CtsRemoteConnectionService"; 45 | public static final String ACCOUNT_ID = "xtstest_CALL_PROVIDER_ID"; 46 | public static final String REMOTE_ACCOUNT_ID = "xtstest_REMOTE_CALL_PROVIDER_ID"; 47 | 48 | public static final String ACCOUNT_LABEL = "CTSConnectionService"; 49 | public static final String REMOTE_ACCOUNT_LABEL = "CTSRemoteConnectionService"; 50 | 51 | private static final String COMMAND_SET_DEFAULT_DIALER = "telecom set-default-dialer "; 52 | 53 | private static final String COMMAND_GET_DEFAULT_DIALER = "telecom get-default-dialer"; 54 | 55 | private static final String COMMAND_GET_SYSTEM_DIALER = "telecom get-system-dialer"; 56 | 57 | private static final String COMMAND_ENABLE = "telecom set-phone-account-enabled "; 58 | 59 | private static final String COMMAND_REGISTER_SIM = "telecom register-sim-phone-account "; 60 | 61 | public static final String MERGE_CALLER_NAME = "calls-merged"; 62 | public static final String SWAP_CALLER_NAME = "calls-swapped"; 63 | 64 | public static boolean shouldTestTelecom(Context context) { 65 | if (!HAS_TELECOM) { 66 | return false; 67 | } 68 | final PackageManager pm = context.getPackageManager(); 69 | return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) && 70 | pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE); 71 | } 72 | 73 | public static String setDefaultDialer(Instrumentation instrumentation, String packageName) 74 | throws Exception { 75 | return executeShellCommand(instrumentation, COMMAND_SET_DEFAULT_DIALER + packageName); 76 | } 77 | 78 | public static String getDefaultDialer(Instrumentation instrumentation) throws Exception { 79 | return executeShellCommand(instrumentation, COMMAND_GET_DEFAULT_DIALER); 80 | } 81 | 82 | public static String getSystemDialer(Instrumentation instrumentation) throws Exception { 83 | return executeShellCommand(instrumentation, COMMAND_GET_SYSTEM_DIALER); 84 | } 85 | 86 | public static void enablePhoneAccount(Instrumentation instrumentation, 87 | PhoneAccountHandle handle) throws Exception { 88 | final ComponentName component = handle.getComponentName(); 89 | executeShellCommand(instrumentation, COMMAND_ENABLE 90 | + component.getPackageName() + "/" + component.getClassName() + " " 91 | + handle.getId()); 92 | } 93 | 94 | public static void registerSimPhoneAccount(Instrumentation instrumentation, 95 | PhoneAccountHandle handle, String label, String address) throws Exception { 96 | final ComponentName component = handle.getComponentName(); 97 | executeShellCommand(instrumentation, COMMAND_REGISTER_SIM 98 | + component.getPackageName() + "/" + component.getClassName() + " " 99 | + handle.getId() + " " + label + " " + address); 100 | } 101 | 102 | /** 103 | * Executes the given shell command and returns the output in a string. Note that even 104 | * if we don't care about the output, we have to read the stream completely to make the 105 | * command execute. 106 | */ 107 | public static String executeShellCommand(Instrumentation instrumentation, 108 | String command) throws Exception { 109 | final ParcelFileDescriptor pfd = 110 | instrumentation.getUiAutomation().executeShellCommand(command); 111 | BufferedReader br = null; 112 | try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) { 113 | br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); 114 | String str = null; 115 | StringBuilder out = new StringBuilder(); 116 | while ((str = br.readLine()) != null) { 117 | out.append(str); 118 | } 119 | return out.toString(); 120 | } finally { 121 | if (br != null) { 122 | closeQuietly(br); 123 | } 124 | closeQuietly(pfd); 125 | } 126 | } 127 | 128 | private static void closeQuietly(AutoCloseable closeable) { 129 | if (closeable != null) { 130 | try { 131 | closeable.close(); 132 | } catch (RuntimeException rethrown) { 133 | throw rethrown; 134 | } catch (Exception ignored) { 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /androidcontacts/src/androidTest/java/com/tomash/androidcontacts/utils/TestUtils.kt: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.utils 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.net.Uri 6 | import android.provider.ContactsContract 7 | import android.provider.MediaStore 8 | import com.tomash.androidcontacts.contactgetter.entity.Address 9 | import com.tomash.androidcontacts.contactgetter.entity.ContactData 10 | import com.tomash.androidcontacts.contactgetter.entity.Email 11 | import com.tomash.androidcontacts.contactgetter.entity.IMAddress 12 | import com.tomash.androidcontacts.contactgetter.entity.Organization 13 | import com.tomash.androidcontacts.contactgetter.entity.PhoneNumber 14 | import com.tomash.androidcontacts.contactgetter.entity.Relation 15 | import com.tomash.androidcontacts.contactgetter.entity.SpecialDate 16 | import com.tomash.androidcontacts.contactgetter.interfaces.WithLabel 17 | import com.tomash.androidcontacts.contactgetter.main.ContactDataFactory 18 | import com.tomash.androidcontacts.contactgetter.main.contactsGetter.ContactsGetterBuilder 19 | import com.tomash.androidcontacts.contactgetter.main.contactsSaver.ContactsSaverBuilder 20 | import org.assertj.core.api.Assertions 21 | import org.junit.Assert 22 | import java.security.SecureRandom 23 | 24 | lateinit var context: Context 25 | private val secureRandom = SecureRandom() 26 | 27 | @Throws(Exception::class) 28 | fun generateRandomWithLabel(clazz: Class): T { 29 | val mainData = randomString(8) 30 | return if (secureRandom.nextBoolean()) { 31 | val labelName = randomString(4) 32 | clazz.getConstructor(String::class.java, String::class.java).newInstance(mainData, labelName) 33 | } else { 34 | val labelId = secureRandom.nextInt(3) + 1 35 | return clazz.getConstructor(Context::class.java, 36 | String::class.java, 37 | Int::class.javaPrimitiveType) 38 | .newInstance(context, mainData, labelId) 39 | } 40 | } 41 | 42 | fun createRandomContactData(bitmapSize: Int = 1): ContactData { 43 | val contactData = ContactDataFactory.createEmpty() 44 | contactData.updatedBitmap = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888) 45 | contactData.compositeName = randomString() 46 | contactData.nickName = randomString() 47 | contactData.sipAddress = randomString() 48 | contactData.accountType = randomString() 49 | contactData.accountName = randomString() 50 | contactData.organization = Organization(randomString(), randomString()) 51 | contactData.note = randomString() 52 | contactData.phoneList.add(generateRandomWithLabel(PhoneNumber::class.java)) 53 | contactData.phoneList.add(generateRandomWithLabel(PhoneNumber::class.java)) 54 | contactData.phoneList.add(generateRandomWithLabel(PhoneNumber::class.java)) 55 | contactData.addressesList.add(generateRandomWithLabel(Address::class.java)) 56 | contactData.addressesList.add(generateRandomWithLabel(Address::class.java)) 57 | contactData.addressesList.add(generateRandomWithLabel(Address::class.java)) 58 | contactData.emailList.add(generateRandomWithLabel(Email::class.java)) 59 | contactData.emailList.add(generateRandomWithLabel(Email::class.java)) 60 | contactData.emailList.add(generateRandomWithLabel(Email::class.java)) 61 | contactData.relationsList.add(generateRandomWithLabel(Relation::class.java)) 62 | contactData.relationsList.add(generateRandomWithLabel(Relation::class.java)) 63 | contactData.relationsList.add(generateRandomWithLabel(Relation::class.java)) 64 | contactData.specialDatesList.add(generateRandomWithLabel(SpecialDate::class.java)) 65 | contactData.specialDatesList.add(generateRandomWithLabel(SpecialDate::class.java)) 66 | contactData.specialDatesList.add(generateRandomWithLabel(SpecialDate::class.java)) 67 | contactData.imAddressesList.add(generateRandomWithLabel(IMAddress::class.java)) 68 | contactData.imAddressesList.add(generateRandomWithLabel(IMAddress::class.java)) 69 | contactData.imAddressesList.add(generateRandomWithLabel(IMAddress::class.java)) 70 | contactData.websitesList.add(randomString()) 71 | contactData.websitesList.add(randomString()) 72 | contactData.websitesList.add(randomString()) 73 | return contactData 74 | } 75 | 76 | fun mockContactDataList(size: Int = 50, 77 | init: ContactData.() -> Unit = {}) = 78 | MutableList(size) { ContactDataFactory.createEmpty().apply(init) } 79 | 80 | fun clearAllContacts() { 81 | //another method for deleting contacts not to intersect with code from library 82 | val cr = context.contentResolver 83 | val cur = cr.query(ContactsContract.Contacts.CONTENT_URI, 84 | null, null, null, null) 85 | while (cur!!.moveToNext()) { 86 | try { 87 | val lookupKey = cur.getString(cur.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)) 88 | val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey) 89 | cr.delete(uri, null, null) 90 | } catch (e: java.lang.Exception) { 91 | } 92 | } 93 | } 94 | 95 | fun assertContacts(userContact: ContactData, contactFromDb: ContactData, bitmapSize: Int) { 96 | val saved = getBitmapFromContactData(contactFromDb) 97 | Assert.assertTrue(saved.height == bitmapSize && saved.width == bitmapSize) 98 | userContact.compositeName isEqual contactFromDb.compositeName 99 | userContact.note isEqual contactFromDb.note 100 | userContact.nickName isEqual contactFromDb.nickName 101 | userContact.sipAddress isEqual contactFromDb.sipAddress 102 | userContact.organization isEqual contactFromDb.organization 103 | userContact.accountType isEqual contactFromDb.accountType 104 | userContact.accountName isEqual contactFromDb.accountName 105 | Assertions.assertThat(userContact.emailList).containsExactlyInAnyOrderElementsOf(contactFromDb.emailList) 106 | Assertions.assertThat(userContact.phoneList).containsExactlyInAnyOrderElementsOf(contactFromDb.phoneList) 107 | Assertions.assertThat(userContact.addressesList).containsExactlyInAnyOrderElementsOf(contactFromDb.addressesList) 108 | Assertions.assertThat(userContact.websitesList).containsExactlyInAnyOrderElementsOf(contactFromDb.websitesList) 109 | Assertions.assertThat(userContact.imAddressesList).containsExactlyInAnyOrderElementsOf(contactFromDb.imAddressesList) 110 | Assertions.assertThat(userContact.specialDatesList).containsExactlyInAnyOrderElementsOf(contactFromDb.specialDatesList) 111 | Assertions.assertThat(userContact.relationsList).containsExactlyInAnyOrderElementsOf(contactFromDb.relationsList) 112 | } 113 | 114 | infix fun T.isEqual(target: T) = 115 | Assert.assertEquals(this, target) 116 | 117 | fun getAllContacts() = ContactsGetterBuilder(context) 118 | .allFields() 119 | .buildList() 120 | 121 | fun List.saveAll() = ContactsSaverBuilder(context) 122 | .saveContactsList(this) 123 | 124 | fun getBitmapFromContactData(contactFromDb: ContactData): Bitmap { 125 | return MediaStore.Images.Media.getBitmap(context.contentResolver, contactFromDb.photoUri) 126 | } 127 | -------------------------------------------------------------------------------- /androidcontacts/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/acresult/ACResult.kt: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.acresult 2 | 3 | /** 4 | * Result of doing some contact related work 5 | * Main contract is: 6 | * If everything goes fine - [onCompleted] is called 7 | * If something errors - [onFailure] is called 8 | * In any case [doFinally] is called 9 | * For any result [onResult] is called 10 | * So you are guarantied to get or [onCompleted] or [onFailure] 11 | */ 12 | class ACResult { 13 | private var doFinally: () -> Unit = {} 14 | private var onResult: (Result) -> Unit = {} 15 | private var onCompleted: () -> Unit = { doFinally() } 16 | private var onFailure: (Exception) -> Unit = { doFinally() } 17 | 18 | /** 19 | * Can be called multiple times with all received results 20 | * It could be called if exception occurred and operation was done partially 21 | */ 22 | fun onResult(func: (Result) -> Unit) { 23 | onResult = func 24 | } 25 | 26 | /** 27 | * Called when some kind of exception is happened during operation 28 | */ 29 | fun onFailure(func: (Exception) -> Unit) { 30 | onFailure = { 31 | func(it) 32 | doFinally() 33 | } 34 | } 35 | 36 | /** 37 | * Called when operation is completed fully successfully 38 | */ 39 | fun onCompleted(func: () -> Unit) { 40 | onCompleted = { 41 | func() 42 | doFinally() 43 | } 44 | } 45 | 46 | /** 47 | * Called anyway when operation is finished 48 | */ 49 | fun doFinally(func: () -> Unit) { 50 | doFinally = func 51 | } 52 | 53 | private fun result(data: Result) { 54 | onResult(data) 55 | } 56 | 57 | private fun failure(data: Exception) { 58 | onFailure(data) 59 | } 60 | 61 | private fun completed() { 62 | onCompleted() 63 | } 64 | 65 | companion object { 66 | internal fun (ACResult.() -> Unit).failure(data: Exception) = 67 | ACResult().apply(this).failure(data) 68 | 69 | internal fun (ACResult.() -> Unit).result(data: Result) = 70 | ACResult().apply(this).result(data) 71 | 72 | internal fun (ACResult.() -> Unit).completed() = 73 | ACResult().apply(this).completed() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/entity/Address.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.entity; 2 | 3 | import android.content.Context; 4 | import android.provider.ContactsContract; 5 | import com.tomash.androidcontacts.contactgetter.interfaces.WithLabel; 6 | 7 | public class Address extends WithLabel { 8 | 9 | public static final int TYPE_HOME = 1; 10 | public static final int TYPE_WORK = 2; 11 | public static final int TYPE_OTHER = 3; 12 | 13 | 14 | public Address(String mainData, String labelName) { 15 | super(mainData, labelName); 16 | } 17 | 18 | public Address(Context ctx, String mainData, int labelId) { 19 | super(ctx, mainData, labelId); 20 | } 21 | 22 | public Address(Context ctx, String mainData) { 23 | super(ctx, mainData); 24 | } 25 | 26 | @Override 27 | protected String getLabelNameResId(Context ctx, int id) { 28 | return ctx.getString(ContactsContract.CommonDataKinds.StructuredPostal.getTypeLabelResource(id)); 29 | } 30 | 31 | @Override 32 | protected int getDefaultLabelId() { 33 | return TYPE_HOME; 34 | } 35 | 36 | @Override 37 | protected boolean isValidLabel(int id) { 38 | return id >= 1 && id <= 3; 39 | } 40 | 41 | @Override 42 | public int getCustomLabelId() { 43 | return 0; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/entity/ContactData.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.entity; 2 | 3 | import android.graphics.Bitmap; 4 | import android.net.Uri; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | 10 | public abstract class ContactData { 11 | private int contactId; 12 | private String lookupKey; 13 | private List emailList = new ArrayList<>(); 14 | private List phoneList = new ArrayList<>(); 15 | private List
addressesList = new ArrayList<>(); 16 | private List websitesList = new ArrayList<>(); 17 | private List imAddressesList = new ArrayList<>(); 18 | private List relationsList = new ArrayList<>(); 19 | private List specialDatesList = new ArrayList<>(); 20 | private List groupList = new ArrayList<>(); 21 | private String note = ""; 22 | private String nickName = ""; 23 | private String sipAddress = ""; 24 | private Uri photoUri = Uri.EMPTY; 25 | private Organization organization = new Organization(); 26 | private NameData nameData = new NameData(); 27 | private String compositeName; 28 | private String accountName; 29 | private String accountType; 30 | private long lastModificationDate; 31 | private Uri updatedPhotoUri; 32 | private Bitmap updatedBitmap; 33 | private boolean isFavorite; 34 | 35 | public int getContactId() { 36 | return contactId; 37 | } 38 | 39 | public ContactData setContactId(int contactId) { 40 | this.contactId = contactId; 41 | return this; 42 | } 43 | 44 | public List getEmailList() { 45 | return emailList; 46 | } 47 | 48 | public ContactData setEmailList(List emailList) { 49 | if (emailList == null) return this; 50 | this.emailList = emailList; 51 | return this; 52 | } 53 | 54 | public List getPhoneList() { 55 | return phoneList; 56 | } 57 | 58 | public ContactData setPhoneList(List phoneList) { 59 | if (phoneList == null) return this; 60 | this.phoneList = phoneList; 61 | return this; 62 | } 63 | 64 | /** 65 | * Returns {@link Uri#EMPTY} in case of no photo , otherwise returns Uri with photo for current contact 66 | */ 67 | public Uri getPhotoUri() { 68 | return photoUri; 69 | } 70 | 71 | public ContactData setPhotoUri(Uri photoUri) { 72 | if (photoUri == null) return this; 73 | if (photoUri != Uri.EMPTY) 74 | updatedPhotoUri = photoUri; 75 | this.photoUri = photoUri; 76 | return this; 77 | } 78 | 79 | public Uri getUpdatedPhotoUri() { 80 | return updatedPhotoUri; 81 | } 82 | 83 | /** 84 | * Used to specify bitmap uri that should be used for creating this contact 85 | * Will be nullified as soon as contact will be created 86 | */ 87 | public ContactData setUpdatedPhotoUri(Uri updatedPhotoUri) { 88 | this.updatedPhotoUri = updatedPhotoUri; 89 | return this; 90 | } 91 | 92 | /** 93 | * Use {@link ContactData#getPhotoUri()} to get real URI of this contact 94 | */ 95 | public Bitmap getUpdatedBitmap() { 96 | return updatedBitmap; 97 | } 98 | 99 | /** 100 | * Used only to specify directly bitmap that should be used to upload this contact to db1 101 | */ 102 | public ContactData setUpdatedBitmap(Bitmap updatedBitmap) { 103 | this.updatedBitmap = updatedBitmap; 104 | return this; 105 | } 106 | 107 | public List
getAddressesList() { 108 | return addressesList; 109 | } 110 | 111 | public ContactData setAddressesList(List
addressesList) { 112 | if (addressesList == null) return this; 113 | this.addressesList = addressesList; 114 | return this; 115 | } 116 | 117 | public String getCompositeName() { 118 | return compositeName; 119 | } 120 | 121 | public ContactData setCompositeName(String compositeName) { 122 | this.compositeName = compositeName; 123 | return this; 124 | } 125 | 126 | public List getWebsitesList() { 127 | return websitesList; 128 | } 129 | 130 | public ContactData setWebsitesList(List websitesList) { 131 | if (websitesList == null) return this; 132 | this.websitesList = websitesList; 133 | return this; 134 | } 135 | 136 | public String getNote() { 137 | return note; 138 | } 139 | 140 | public ContactData setNote(String note) { 141 | if (note == null) return this; 142 | this.note = note; 143 | return this; 144 | } 145 | 146 | /** 147 | *

148 | * Gets last modification timestamp in Unix time 149 | *

150 | *

151 | * AVAILABLE FROM 18 API 152 | *

153 | */ 154 | public long getLastModificationDate() { 155 | return lastModificationDate; 156 | } 157 | 158 | public ContactData setLastModificationDate(long lastModificationDate) { 159 | this.lastModificationDate = lastModificationDate; 160 | return this; 161 | } 162 | 163 | public List getImAddressesList() { 164 | return imAddressesList; 165 | } 166 | 167 | public ContactData setImAddressesList(List imAddressesList) { 168 | if (imAddressesList == null) return this; 169 | this.imAddressesList = imAddressesList; 170 | return this; 171 | } 172 | 173 | public List getRelationsList() { 174 | return relationsList; 175 | } 176 | 177 | public ContactData setRelationsList(List relationsList) { 178 | if (relationsList == null) return this; 179 | this.relationsList = relationsList; 180 | return this; 181 | } 182 | 183 | public List getSpecialDatesList() { 184 | return specialDatesList; 185 | } 186 | 187 | public ContactData setSpecialDatesList(List specialDatesList) { 188 | if (specialDatesList == null) return this; 189 | this.specialDatesList = specialDatesList; 190 | return this; 191 | } 192 | 193 | public String getNickName() { 194 | return nickName; 195 | } 196 | 197 | public ContactData setNickName(String nickName) { 198 | if (nickName == null) return this; 199 | this.nickName = nickName; 200 | return this; 201 | } 202 | 203 | public String getSipAddress() { 204 | return sipAddress; 205 | } 206 | 207 | public ContactData setSipAddress(String sipAddress) { 208 | if (sipAddress == null) return this; 209 | this.sipAddress = sipAddress; 210 | return this; 211 | } 212 | 213 | public Organization getOrganization() { 214 | return organization; 215 | } 216 | 217 | public ContactData setOrganization(Organization organization) { 218 | if (organization == null) return this; 219 | this.organization = organization; 220 | return this; 221 | } 222 | 223 | public NameData getNameData() { 224 | return nameData; 225 | } 226 | 227 | public ContactData setNameData(NameData nameData) { 228 | if (nameData == null) return this; 229 | this.nameData = nameData; 230 | return this; 231 | } 232 | 233 | public ContactData setFavorite(boolean favorite) { 234 | isFavorite = favorite; 235 | return this; 236 | } 237 | 238 | public boolean isFavorite() { 239 | return isFavorite; 240 | } 241 | 242 | public List getGroupList() { 243 | return groupList; 244 | } 245 | 246 | public ContactData setGroupList(List groupList) { 247 | if (groupList == null) return this; 248 | this.groupList = groupList; 249 | return this; 250 | } 251 | 252 | public String getAccountName() { 253 | return accountName; 254 | } 255 | 256 | public ContactData setAccountName(String accountName) { 257 | this.accountName = accountName; 258 | return this; 259 | } 260 | 261 | public String getAccountType() { 262 | return accountType; 263 | } 264 | 265 | public ContactData setAccountType(String accountType) { 266 | this.accountType = accountType; 267 | return this; 268 | } 269 | 270 | @Override 271 | public boolean equals(Object o) { 272 | if (this == o) return true; 273 | if (o == null || getClass() != o.getClass()) return false; 274 | 275 | ContactData contact = (ContactData) o; 276 | 277 | if (contactId != contact.contactId) return false; 278 | if (!emailList.equals(contact.emailList)) return false; 279 | if (!phoneList.equals(contact.phoneList)) return false; 280 | if (!addressesList.equals(contact.addressesList)) return false; 281 | if (!websitesList.equals(contact.websitesList)) return false; 282 | if (!imAddressesList.equals(contact.imAddressesList)) return false; 283 | if (!relationsList.equals(contact.relationsList)) return false; 284 | if (!specialDatesList.equals(contact.specialDatesList)) return false; 285 | if (!groupList.equals(contact.groupList)) return false; 286 | if (!note.equals(contact.note)) return false; 287 | if (!nickName.equals(contact.nickName)) return false; 288 | if (!sipAddress.equals(contact.sipAddress)) return false; 289 | if (!photoUri.equals(contact.photoUri)) return false; 290 | if (!organization.equals(contact.organization)) return false; 291 | if (!nameData.equals(contact.nameData)) return false; 292 | if (isFavorite != contact.isFavorite) return false; 293 | return (compositeName == null ? contact.compositeName == null : compositeName.equals(contact.compositeName)); 294 | } 295 | 296 | public String getLookupKey() { 297 | return lookupKey; 298 | } 299 | 300 | public ContactData setLookupKey(String lookupKey) { 301 | this.lookupKey = lookupKey; 302 | return this; 303 | } 304 | 305 | @Override 306 | public int hashCode() { 307 | return contactId; 308 | } 309 | } -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/entity/Email.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.entity; 2 | 3 | import android.content.Context; 4 | import android.provider.ContactsContract; 5 | import com.tomash.androidcontacts.contactgetter.interfaces.WithLabel; 6 | 7 | public class Email extends WithLabel { 8 | public static final int TYPE_HOME = 1; 9 | public static final int TYPE_WORK = 2; 10 | public static final int TYPE_OTHER = 3; 11 | public static final int TYPE_MOBILE = 4; 12 | 13 | public Email(String mainData, String labelName) { 14 | super(mainData, labelName); 15 | } 16 | 17 | public Email(Context ctx, String mainData, int labelId) { 18 | super(ctx, mainData, labelId); 19 | } 20 | 21 | public Email(Context ctx, String mainData) { 22 | super(ctx, mainData); 23 | } 24 | 25 | @Override 26 | protected String getLabelNameResId(Context ctx, int id) { 27 | return ctx.getString(ContactsContract.CommonDataKinds.Email.getTypeLabelResource(id)); 28 | } 29 | 30 | @Override 31 | protected int getDefaultLabelId() { 32 | return TYPE_HOME; 33 | } 34 | 35 | @Override 36 | protected boolean isValidLabel(int id) { 37 | return id >= 1 && id <= 4; 38 | } 39 | 40 | @Override 41 | public int getCustomLabelId() { 42 | return 0; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/entity/Group.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.entity; 2 | 3 | public class Group { 4 | private int groupId; 5 | private String groupTitle = ""; 6 | 7 | public int getGroupId() { 8 | return groupId; 9 | } 10 | 11 | public Group setGroupId(int groupId) { 12 | this.groupId = groupId; 13 | return this; 14 | } 15 | 16 | public String getGroupTitle() { 17 | return groupTitle; 18 | } 19 | 20 | public Group setGroupTitle(String groupTitle) { 21 | if (groupTitle == null) return this; 22 | this.groupTitle = groupTitle; 23 | return this; 24 | } 25 | 26 | @Override 27 | public boolean equals(Object o) { 28 | if (this == o) return true; 29 | if (o == null || getClass() != o.getClass()) return false; 30 | 31 | Group group = (Group) o; 32 | 33 | if (groupId != group.groupId) return false; 34 | return groupTitle.equals(group.groupTitle); 35 | 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | int result = groupId; 41 | result = 31 * result + groupTitle.hashCode(); 42 | return result; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/entity/IMAddress.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.entity; 2 | 3 | import android.content.Context; 4 | import android.provider.ContactsContract; 5 | import com.tomash.androidcontacts.contactgetter.interfaces.WithLabel; 6 | 7 | public class IMAddress extends WithLabel { 8 | public static final int PROTOCOL_AIM = 0; 9 | public static final int PROTOCOL_MSN = 1; 10 | public static final int PROTOCOL_YAHOO = 2; 11 | public static final int PROTOCOL_SKYPE = 3; 12 | public static final int PROTOCOL_QQ = 4; 13 | public static final int PROTOCOL_GOOGLE_TALK = 5; 14 | public static final int PROTOCOL_ICQ = 6; 15 | public static final int PROTOCOL_JABBER = 7; 16 | public static final int PROTOCOL_NETMEETING = 8; 17 | 18 | public IMAddress(String mainData, String labelName) { 19 | super(mainData, labelName); 20 | } 21 | 22 | public IMAddress(Context ctx, String mainData, int labelId) { 23 | super(ctx, mainData, labelId); 24 | } 25 | 26 | public IMAddress(Context ctx, String mainData) { 27 | super(ctx, mainData); 28 | } 29 | 30 | @Override 31 | protected String getLabelNameResId(Context ctx, int id) { 32 | return ctx.getString(ContactsContract.CommonDataKinds.Im.getProtocolLabelResource(id)); 33 | } 34 | 35 | @Override 36 | protected int getDefaultLabelId() { 37 | return PROTOCOL_AIM; 38 | } 39 | 40 | @Override 41 | protected boolean isValidLabel(int id) { 42 | return id >= 0 && id <= 8; 43 | } 44 | 45 | @Override 46 | public int getCustomLabelId() { 47 | return -1; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/entity/NameData.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.entity; 2 | 3 | public class NameData { 4 | private String fullName = ""; 5 | private String firstName = ""; 6 | private String surname = ""; 7 | private String namePrefix = ""; 8 | private String middleName = ""; 9 | private String nameSuffix = ""; 10 | private String phoneticFirst = ""; 11 | private String phoneticMiddle = ""; 12 | private String phoneticLast = ""; 13 | 14 | public String getFullName() { 15 | return fullName; 16 | } 17 | 18 | public NameData setFullName(String fullName) { 19 | if (fullName == null) return this; 20 | this.fullName = fullName; 21 | return this; 22 | } 23 | 24 | public String getFirstName() { 25 | return firstName; 26 | } 27 | 28 | public NameData setFirstName(String firstName) { 29 | if (firstName == null) return this; 30 | this.firstName = firstName; 31 | return this; 32 | } 33 | 34 | public String getSurname() { 35 | return surname; 36 | } 37 | 38 | public NameData setSurname(String surname) { 39 | if (surname == null) return this; 40 | this.surname = surname; 41 | return this; 42 | } 43 | 44 | public String getNamePrefix() { 45 | return namePrefix; 46 | } 47 | 48 | public NameData setNamePrefix(String namePrefix) { 49 | if (namePrefix == null) return this; 50 | this.namePrefix = namePrefix; 51 | return this; 52 | } 53 | 54 | public String getMiddleName() { 55 | return middleName; 56 | } 57 | 58 | public NameData setMiddleName(String middleName) { 59 | if (middleName == null) return this; 60 | this.middleName = middleName; 61 | return this; 62 | } 63 | 64 | public String getNameSuffix() { 65 | return nameSuffix; 66 | } 67 | 68 | public NameData setNameSuffix(String nameSuffix) { 69 | if (nameSuffix == null) return this; 70 | this.nameSuffix = nameSuffix; 71 | return this; 72 | } 73 | 74 | public String getPhoneticFirst() { 75 | return phoneticFirst; 76 | } 77 | 78 | public NameData setPhoneticFirst(String phoneticFirst) { 79 | if (phoneticFirst == null) return this; 80 | this.phoneticFirst = phoneticFirst; 81 | return this; 82 | } 83 | 84 | public String getPhoneticMiddle() { 85 | return phoneticMiddle; 86 | } 87 | 88 | public NameData setPhoneticMiddle(String phoneticMiddle) { 89 | if (phoneticMiddle == null) return this; 90 | this.phoneticMiddle = phoneticMiddle; 91 | return this; 92 | } 93 | 94 | public String getPhoneticLast() { 95 | return phoneticLast; 96 | } 97 | 98 | public NameData setPhoneticLast(String phoneticLast) { 99 | if (phoneticLast == null) return this; 100 | this.phoneticLast = phoneticLast; 101 | return this; 102 | } 103 | 104 | @Override 105 | public boolean equals(Object o) { 106 | if (this == o) return true; 107 | if (o == null || getClass() != o.getClass()) return false; 108 | 109 | NameData nameData = (NameData) o; 110 | 111 | if (!fullName.equals(nameData.fullName)) return false; 112 | if (!firstName.equals(nameData.firstName)) return false; 113 | if (!surname.equals(nameData.surname)) return false; 114 | if (!namePrefix.equals(nameData.namePrefix)) return false; 115 | if (!middleName.equals(nameData.middleName)) return false; 116 | if (!nameSuffix.equals(nameData.nameSuffix)) return false; 117 | if (!phoneticFirst.equals(nameData.phoneticFirst)) return false; 118 | if (!phoneticMiddle.equals(nameData.phoneticMiddle)) return false; 119 | return phoneticLast.equals(nameData.phoneticLast); 120 | 121 | } 122 | 123 | @Override 124 | public int hashCode() { 125 | return fullName.hashCode(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/entity/Organization.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.entity; 2 | 3 | public class Organization { 4 | private String name = ""; 5 | private String title = ""; 6 | 7 | public String getName() { 8 | return name; 9 | } 10 | 11 | public Organization setName(String name) { 12 | if (name == null) return this; 13 | this.name = name; 14 | return this; 15 | } 16 | 17 | public Organization(String name, String title) { 18 | setName(name); 19 | setTitle(title); 20 | } 21 | 22 | public Organization() { 23 | } 24 | 25 | public String getTitle() { 26 | return title; 27 | } 28 | 29 | public Organization setTitle(String title) { 30 | if (title == null) return this; 31 | this.title = title; 32 | return this; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (this == o) return true; 38 | if (o == null || getClass() != o.getClass()) return false; 39 | 40 | Organization that = (Organization) o; 41 | 42 | if (!name.equals(that.name)) return false; 43 | return title.equals(that.title); 44 | 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | int result = name.hashCode(); 50 | result = 31 * result + title.hashCode(); 51 | return result; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/entity/PhoneNumber.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.entity; 2 | 3 | import android.content.Context; 4 | import android.provider.ContactsContract; 5 | import com.tomash.androidcontacts.contactgetter.interfaces.WithLabel; 6 | 7 | public class PhoneNumber extends WithLabel { 8 | 9 | private boolean isPrimary; 10 | 11 | public PhoneNumber(String mainData, String labelName) { 12 | super(mainData, labelName); 13 | } 14 | 15 | public PhoneNumber(Context ctx, String mainData, int labelId) { 16 | super(ctx, mainData, labelId); 17 | } 18 | 19 | public PhoneNumber(Context ctx, String mainData) { 20 | super(ctx, mainData); 21 | } 22 | 23 | @Override 24 | protected String getLabelNameResId(Context ctx, int id) { 25 | return ctx.getString(ContactsContract.CommonDataKinds.Phone.getTypeLabelResource(id)); 26 | } 27 | 28 | @Override 29 | protected int getDefaultLabelId() { 30 | return TYPE_HOME; 31 | } 32 | 33 | @Override 34 | protected boolean isValidLabel(int id) { 35 | return id >= 0 && id <= 20; 36 | } 37 | 38 | @Override 39 | public int getCustomLabelId() { 40 | return 0; 41 | } 42 | 43 | public boolean isPrimary() { 44 | return isPrimary; 45 | } 46 | 47 | public PhoneNumber setPrimary(boolean isPrimary) { 48 | this.isPrimary = isPrimary; 49 | return this; 50 | } 51 | 52 | @Override 53 | public boolean equals(Object o) { 54 | if (this == o) return true; 55 | if (!(o instanceof PhoneNumber)) return false; 56 | if (!super.equals(o)) return false; 57 | 58 | PhoneNumber that = (PhoneNumber) o; 59 | 60 | return isPrimary == that.isPrimary; 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | int result = super.hashCode(); 66 | result = 31 * result + (isPrimary ? 1 : 0); 67 | return result; 68 | } 69 | 70 | public static final int TYPE_HOME = 1; 71 | public static final int TYPE_MOBILE = 2; 72 | public static final int TYPE_WORK = 3; 73 | public static final int TYPE_FAX_WORK = 4; 74 | public static final int TYPE_FAX_HOME = 5; 75 | public static final int TYPE_PAGER = 6; 76 | public static final int TYPE_OTHER = 7; 77 | public static final int TYPE_CALLBACK = 8; 78 | public static final int TYPE_CAR = 9; 79 | public static final int TYPE_COMPANY_MAIN = 10; 80 | public static final int TYPE_ISDN = 11; 81 | public static final int TYPE_MAIN = 12; 82 | public static final int TYPE_OTHER_FAX = 13; 83 | public static final int TYPE_RADIO = 14; 84 | public static final int TYPE_TELEX = 15; 85 | public static final int TYPE_TTY_TDD = 16; 86 | public static final int TYPE_WORK_MOBILE = 17; 87 | public static final int TYPE_WORK_PAGER = 18; 88 | public static final int TYPE_ASSISTANT = 19; 89 | public static final int TYPE_MMS = 20; 90 | } 91 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/entity/Relation.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.entity; 2 | 3 | import android.content.Context; 4 | import android.provider.ContactsContract; 5 | import com.tomash.androidcontacts.contactgetter.interfaces.WithLabel; 6 | 7 | public class Relation extends WithLabel { 8 | 9 | public static final int TYPE_ASSISTANT = 1; 10 | public static final int TYPE_BROTHER = 2; 11 | public static final int TYPE_CHILD = 3; 12 | public static final int TYPE_DOMESTIC_PARTNER = 4; 13 | public static final int TYPE_FATHER = 5; 14 | public static final int TYPE_FRIEND = 6; 15 | public static final int TYPE_MANAGER = 7; 16 | public static final int TYPE_MOTHER = 8; 17 | public static final int TYPE_PARENT = 9; 18 | public static final int TYPE_PARTNER = 10; 19 | public static final int TYPE_REFERRED_BY = 11; 20 | public static final int TYPE_RELATIVE = 12; 21 | public static final int TYPE_SISTER = 13; 22 | public static final int TYPE_SPOUSE = 14; 23 | 24 | 25 | public Relation(String mainData, String labelName) { 26 | super(mainData, labelName); 27 | } 28 | 29 | public Relation(Context ctx, String mainData, int labelId) { 30 | super(ctx, mainData, labelId); 31 | } 32 | 33 | public Relation(Context ctx, String mainData) { 34 | super(ctx, mainData); 35 | } 36 | 37 | @Override 38 | protected String getLabelNameResId(Context ctx, int id) { 39 | return ctx.getString(ContactsContract.CommonDataKinds.Relation.getTypeLabelResource(id)); 40 | } 41 | 42 | @Override 43 | protected int getDefaultLabelId() { 44 | return TYPE_ASSISTANT; 45 | } 46 | 47 | @Override 48 | protected boolean isValidLabel(int id) { 49 | return id >= 1 && id <= 14; 50 | } 51 | 52 | @Override 53 | public int getCustomLabelId() { 54 | return 0; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/entity/SpecialDate.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.entity; 2 | 3 | import android.content.Context; 4 | import android.provider.ContactsContract; 5 | import com.tomash.androidcontacts.contactgetter.interfaces.WithLabel; 6 | 7 | public class SpecialDate extends WithLabel { 8 | public static final int TYPE_ANNIVERSARY = 1; 9 | public static final int TYPE_OTHER = 2; 10 | public static final int TYPE_BIRTHDAY = 3; 11 | 12 | public SpecialDate(String mainData, String labelName) { 13 | super(mainData, labelName); 14 | } 15 | 16 | public SpecialDate(Context ctx, String mainData, int labelId) { 17 | super(ctx, mainData, labelId); 18 | } 19 | 20 | public SpecialDate(Context ctx, String mainData) { 21 | super(ctx, mainData); 22 | } 23 | 24 | @Override 25 | protected String getLabelNameResId(Context ctx, int id) { 26 | return ctx.getString(ContactsContract.CommonDataKinds.Event.getTypeResource(id)); 27 | } 28 | 29 | @Override 30 | protected int getDefaultLabelId() { 31 | return TYPE_ANNIVERSARY; 32 | } 33 | 34 | @Override 35 | protected boolean isValidLabel(int id) { 36 | return id >= 1 && id <= 3; 37 | } 38 | 39 | @Override 40 | public int getCustomLabelId() { 41 | return 0; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/interfaces/BaseFilter.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.interfaces; 2 | 3 | /** 4 | *

5 | * Base class for all filters 6 | *

7 | * 8 | * @param Type of container where from can get filterable data 9 | * @param Type of filterable data 10 | */ 11 | 12 | public abstract class BaseFilter implements Filterable { 13 | /** 14 | *

15 | * This pattern will be used for all future filtering. 16 | *

17 | * 18 | * @return pattern for filtering 19 | */ 20 | protected abstract V getFilterPattern(); 21 | 22 | /** 23 | *

24 | * Used for getting target that should be compared with patter to be filtered or not. 25 | *

26 | * 27 | * @param data container where from data could be obtained 28 | * @return target fro comparing with pattern 29 | */ 30 | protected abstract V getFilterData(T data); 31 | 32 | /** 33 | *

34 | * Should return condition that will filter data 35 | *

36 | * 37 | * @param data target data 38 | * @param pattern pattern to compare with 39 | * @return 40 | */ 41 | protected abstract boolean getFilterCondition(V data, V pattern); 42 | } 43 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/interfaces/FieldFilter.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.interfaces; 2 | 3 | import com.tomash.androidcontacts.contactgetter.entity.ContactData; 4 | 5 | /** 6 | *

7 | * Base class used for filtering data from fieds that are contained in {@link ContactData} object. 8 | *

9 | * 10 | * @param Container for filterable data , for this class always {@link ContactData} object 11 | * @param Type of field you want to filter 12 | */ 13 | 14 | public abstract class FieldFilter extends BaseFilter { 15 | @Override 16 | public boolean passedFilter(ContactData contact) { 17 | return getFilterCondition(getFilterData(contact), getFilterPattern()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/interfaces/Filterable.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.interfaces; 2 | 3 | import com.tomash.androidcontacts.contactgetter.entity.ContactData; 4 | 5 | /** 6 | * This interface defines ability to filter Contacts 7 | */ 8 | interface Filterable { 9 | /** 10 | *

11 | * Main method for filtering contacts 12 | *

13 | * 14 | * @param contact contact what should get exercised with filter 15 | * @return true if should not be filtered , false otherwise 16 | */ 17 | boolean passedFilter(ContactData contact); 18 | } 19 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/interfaces/ListFilter.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.interfaces; 2 | 3 | import com.tomash.androidcontacts.contactgetter.entity.ContactData; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | *

9 | * Abstract class for filtering contacts with fields that are lists. 10 | *

11 | * 12 | * @param type of container of target data 13 | * @param type of field you want to filter 14 | */ 15 | 16 | public abstract class ListFilter extends BaseFilter { 17 | 18 | /** 19 | * User to get container of filterable data 20 | * 21 | * @param contact contact object where from to get data 22 | * @return list with target data 23 | */ 24 | protected abstract List getFilterContainer(ContactData contact); 25 | 26 | @Override 27 | public boolean passedFilter(ContactData contact) { 28 | for (T t : getFilterContainer(contact)) { 29 | if (getFilterCondition(getFilterData(t), getFilterPattern())) 30 | return true; 31 | } 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/interfaces/WithLabel.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.interfaces; 2 | 3 | import android.content.Context; 4 | 5 | public abstract class WithLabel { 6 | private String mainData; 7 | private int contactId; 8 | private int labelId; 9 | private String labelName; 10 | 11 | /** 12 | * Used to create WithLabel objects with custom label 13 | * 14 | * @param mainData main data from this object , e.g. phone number , email 15 | * @param labelName name of custom label 16 | */ 17 | public WithLabel(String mainData, String labelName) { 18 | this.mainData = mainData; 19 | this.contactId = -1; 20 | this.labelId = getCustomLabelId(); 21 | this.labelName = labelName; 22 | } 23 | 24 | /** 25 | *

26 | * Used to create WithLabel objects with specific in label 27 | *

28 | *

29 | * In case of invalid label will use default label for this type of data 30 | *

31 | * 32 | * @param ctx context 33 | * @param mainData main data from this object , e.g. phone number , email 34 | * @param labelId id for label , used to get name for this label with system default language , e.g. {@link com.tomash.androidcontacts.contactgetter.entity.PhoneNumber#TYPE_HOME} 35 | */ 36 | public WithLabel(Context ctx, String mainData, int labelId) { 37 | this.mainData = mainData; 38 | this.contactId = -1; 39 | this.labelId = isValidLabel(labelId) ? labelId : getDefaultLabelId(); 40 | this.labelName = getLabelNameResId(ctx, labelId); 41 | } 42 | 43 | 44 | /** 45 | *

46 | * Used to create WithLabel objects with default label 47 | *

48 | * 49 | * @param ctx context 50 | * @param mainData main data from this object , e.g. phone number , email 51 | */ 52 | public WithLabel(Context ctx, String mainData) { 53 | this.mainData = mainData; 54 | this.contactId = -1; 55 | this.labelId = getDefaultLabelId(); 56 | this.labelName = getLabelNameResId(ctx, labelId); 57 | } 58 | 59 | 60 | /** 61 | * Gets label resource by id 62 | * 63 | * @param id id of this label 64 | * @return string id of this label 65 | */ 66 | protected abstract String getLabelNameResId(Context ctx, int id); 67 | 68 | protected abstract int getDefaultLabelId(); 69 | 70 | protected abstract boolean isValidLabel(int id); 71 | 72 | public abstract int getCustomLabelId(); 73 | 74 | 75 | public WithLabel() { 76 | } 77 | 78 | public String getMainData() { 79 | return mainData; 80 | } 81 | 82 | public int getContactId() { 83 | return contactId; 84 | } 85 | 86 | public int getLabelId() { 87 | return labelId; 88 | } 89 | 90 | public String getLabelName() { 91 | return labelName; 92 | } 93 | 94 | @Override 95 | public boolean equals(Object o) { 96 | if (this == o) return true; 97 | if (o == null || getClass() != o.getClass()) return false; 98 | 99 | WithLabel withLabel = (WithLabel) o; 100 | 101 | if (labelId != withLabel.labelId) return false; 102 | if (!mainData.equals(withLabel.mainData)) return false; 103 | return labelName.equals(withLabel.labelName); 104 | } 105 | 106 | 107 | public WithLabel setMainData(String mainData) { 108 | this.mainData = mainData; 109 | return this; 110 | } 111 | 112 | public WithLabel setContactId(int contactId) { 113 | this.contactId = contactId; 114 | return this; 115 | } 116 | 117 | public WithLabel setLabelId(int labelId) { 118 | this.labelId = labelId; 119 | return this; 120 | } 121 | 122 | public WithLabel setLabelName(String labelName) { 123 | this.labelName = labelName; 124 | return this; 125 | } 126 | 127 | @Override 128 | public int hashCode() { 129 | return mainData.hashCode(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/main/ContactDataFactory.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.main; 2 | 3 | import com.tomash.androidcontacts.contactgetter.entity.ContactData; 4 | 5 | public class ContactDataFactory { 6 | /** 7 | * @return Creates empty contact data object 8 | */ 9 | public static ContactData createEmpty() { 10 | return new ContactData() { 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/main/FieldType.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.main; 2 | 3 | /** 4 | * This enum contains field type that can be enabled while querying contacts. 5 | */ 6 | 7 | public enum FieldType { 8 | EMAILS, 9 | PHONE_NUMBERS, 10 | ADDRESS, 11 | WEBSITES, 12 | IM_ADDRESSES, 13 | SPECIAL_DATES, 14 | NOTES, 15 | RELATIONS, 16 | NICKNAME, 17 | ORGANIZATION, 18 | SIP, 19 | NAME_DATA, 20 | GROUPS 21 | } 22 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/main/Sorting.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.main; 2 | 3 | import android.provider.ContactsContract; 4 | 5 | /** 6 | * Enum represents types of sorting that can be applied to contacts query. 7 | */ 8 | 9 | public enum Sorting { 10 | 11 | 12 | BY_DISPLAY_NAME_ASC { 13 | @Override 14 | public String getSorting() { 15 | return ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"; 16 | } 17 | }, 18 | BY_DISPLAY_NAME_DESC { 19 | @Override 20 | public String getSorting() { 21 | return ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " DESC"; 22 | } 23 | }, 24 | BY_ID_ASC { 25 | @Override 26 | public String getSorting() { 27 | return ContactsContract.CommonDataKinds.Phone._ID + " ASC"; 28 | } 29 | }, 30 | BY_ID_DESC { 31 | @Override 32 | public String getSorting() { 33 | return ContactsContract.CommonDataKinds.Phone._ID + " DESC"; 34 | } 35 | }; 36 | 37 | public abstract String getSorting(); 38 | } 39 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/main/blocked/BlockedContactsManager.kt: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.main.blocked 2 | 3 | import android.content.ContentValues 4 | import android.content.Context 5 | import android.os.Build 6 | import android.provider.BlockedNumberContract.BlockedNumbers 7 | import android.support.annotation.RequiresApi 8 | 9 | /** 10 | * Used to get blocked contacts. 11 | * 12 | * **Can be used only starting from android N.** 13 | * 14 | * Your app should be default dialer to use this, or it will fail with [SecurityException]. 15 | */ 16 | class BlockedContactsManager(private val ctx: Context) { 17 | 18 | /** 19 | * Gets all blocked contacts. 20 | * 21 | * I feel like it is safe to think, that all numbers in that list are unique, but it is handled on android side. 22 | * 23 | * In case of error, it can be handled in [onError], and will return empty list. 24 | */ 25 | @RequiresApi(Build.VERSION_CODES.N) 26 | fun getBlockedNumbers(onError: (Exception) -> Unit = {}): List { 27 | return try { 28 | ctx.contentResolver.query(BlockedNumbers.CONTENT_URI, 29 | arrayOf(BlockedNumbers.COLUMN_ORIGINAL_NUMBER), null, null, null)?.run { 30 | val result = mutableListOf() 31 | while (moveToNext()) { 32 | getString(getColumnIndex(BlockedNumbers.COLUMN_ORIGINAL_NUMBER))?.let { 33 | result.add(it) 34 | } 35 | } 36 | result 37 | } ?: emptyList() 38 | } catch (exception: Exception) { 39 | onError(exception) 40 | emptyList() 41 | } 42 | } 43 | 44 | /** 45 | * Blocks contact. 46 | * 47 | * In case of error, it can be handled in [onError]. 48 | */ 49 | @RequiresApi(Build.VERSION_CODES.N) 50 | fun block(number: String, onError: (Exception) -> Unit = {}) { 51 | val values = ContentValues() 52 | values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number) 53 | try { 54 | ctx.contentResolver.insert(BlockedNumbers.CONTENT_URI, values) 55 | } catch (exception: Exception) { 56 | onError(exception) 57 | } 58 | } 59 | 60 | /** 61 | * Unblocks contact. 62 | * 63 | * Does nothing if this number is not blocked. 64 | * 65 | * In case of error, it can be handled in [onError]. 66 | */ 67 | @RequiresApi(Build.VERSION_CODES.N) 68 | fun unblock(number: String, onError: (Exception) -> Unit = {}) { 69 | val values = ContentValues() 70 | values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number) 71 | try { 72 | ctx.contentResolver.insert(BlockedNumbers.CONTENT_URI, values)?.let { uri -> 73 | ctx.contentResolver.delete(uri, null, null) 74 | } 75 | } catch (exception: Exception) { 76 | onError(exception) 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/main/contactsDeleter/ContactsDeleter.kt: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.main.contactsDeleter 2 | 3 | import android.content.ContentProviderOperation 4 | import android.content.ContentResolver 5 | import android.content.Context 6 | import android.net.Uri 7 | import android.provider.ContactsContract 8 | import com.tomash.androidcontacts.contactgetter.acresult.ACResult 9 | import com.tomash.androidcontacts.contactgetter.acresult.ACResult.Companion.completed 10 | import com.tomash.androidcontacts.contactgetter.acresult.ACResult.Companion.failure 11 | import com.tomash.androidcontacts.contactgetter.acresult.ACResult.Companion.result 12 | import com.tomash.androidcontacts.contactgetter.entity.ContactData 13 | 14 | private class ContactsDeleterImpl(context: Context) : ContactsDeleter { 15 | private val resolver: ContentResolver = context.contentResolver 16 | 17 | override fun deleteContact(contact: ContactData, 18 | func: ACResult.() -> Unit) { 19 | deleteContacts(listOf(contact)) { 20 | onCompleted { func.completed() } 21 | onResult { func.result(it.first()) } 22 | onFailure { 23 | if (it.isNotEmpty()) { 24 | func.failure(it.values.first()) 25 | } 26 | } 27 | } 28 | } 29 | 30 | override fun deleteContacts(contacts: List, 31 | func: ACResult, Map>.() -> Unit) { 32 | try { 33 | if (contacts.isEmpty()) { 34 | return func.completed() 35 | } 36 | val batch = ArrayList(contacts.map { 37 | Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, it.lookupKey) 38 | }.map { ContentProviderOperation.newDelete(it).build() }) 39 | val results = resolver.applyBatch(ContactsContract.AUTHORITY, batch) 40 | val errors = results 41 | .filter { it.count == 0 } 42 | .mapNotNull { contacts.getOrNull(results.indexOf(it)) } 43 | .map { it to UnsuccessfulDeleteException() } 44 | .toMap() 45 | val successes = results 46 | .filter { it.count == 1 } 47 | .mapNotNull { contacts.getOrNull(results.indexOf(it)) } 48 | 49 | errors.takeIf { it.isNotEmpty() } 50 | ?.let { func.failure(it) } 51 | 52 | successes.takeIf { it.isNotEmpty() } 53 | ?.let { func.result(it) } 54 | 55 | if (errors.isEmpty()) { 56 | func.completed() 57 | } 58 | } catch (exception: Exception) { 59 | func.failure(contacts.map { it to exception }.toMap()) 60 | } 61 | } 62 | } 63 | 64 | /** 65 | * Default exception, which is called if no contacts were removed 66 | * This may happen if [ContactData] was not in database 67 | */ 68 | class UnsuccessfulDeleteException : RuntimeException() 69 | 70 | /** 71 | * Used to delete [ContactData] from phone. 72 | * Will throw exception, if you try to delete contacts without permissions. 73 | * Will throw [UnsuccessfulDeleteException] if contact was not deleted( for example if it was not database) 74 | */ 75 | interface ContactsDeleter { 76 | 77 | /** 78 | * Deletes one [ContactData] 79 | * @param func returns deleted [ContactData] in [ACResult.onResult] 80 | * or [ContactData] and [Exception] in [ACResult.onFailure] 81 | */ 82 | fun deleteContact(contact: ContactData, 83 | func: ACResult.() -> Unit = {}) 84 | 85 | /** 86 | * Deletes list of [ContactData] 87 | * @param func returns all deleted [ContactData] in [ACResult.onResult] and 88 | * map with [ContactData] and corresponding error in [ACResult.onFailure] 89 | * 90 | * **example**: 91 | * ``` 92 | * deleteContacts(listOf(ContactData("Andrew"), 93 | * ContactData("Peter"), 94 | * ContactData("John"))) { 95 | * // 1 delete was fine, 2 failed. 96 | * onResult { deletedContacts -> 97 | * // deletedContacts contains ContactData("Andrew") 98 | * } 99 | * onFailure { deletedContactsErrMap -> 100 | * // deletedContactsErrMap contains map with 101 | * // ContactData("Peter") -> UnsuccessfulDeleteException 102 | * // ContactData("John") -> UnsuccessfulDeleteException 103 | * } 104 | * onCompleted { 105 | * // not called because of errors 106 | * } 107 | * doFinally { 108 | * // called anyway 109 | * } 110 | * } 111 | * ``` 112 | */ 113 | fun deleteContacts(contacts: List, 114 | func: ACResult, Map>.() -> Unit = {}) 115 | 116 | companion object { 117 | operator fun invoke(context: Context): ContactsDeleter = ContactsDeleterImpl(context) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/main/contactsGetter/ContactsGetter.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.main.contactsGetter; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.os.Build; 8 | import android.provider.ContactsContract; 9 | import android.provider.ContactsContract.CommonDataKinds; 10 | import android.provider.ContactsContract.CommonDataKinds.*; 11 | import android.util.SparseArray; 12 | import com.tomash.androidcontacts.contactgetter.entity.Email; 13 | import com.tomash.androidcontacts.contactgetter.entity.Organization; 14 | import com.tomash.androidcontacts.contactgetter.entity.Relation; 15 | import com.tomash.androidcontacts.contactgetter.entity.*; 16 | import com.tomash.androidcontacts.contactgetter.interfaces.WithLabel; 17 | import com.tomash.androidcontacts.contactgetter.main.FieldType; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import static android.provider.ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE; 23 | import static android.provider.ContactsContract.CommonDataKinds.Organization.TITLE; 24 | 25 | class ContactsGetter { 26 | private ContentResolver mResolver; 27 | private Context mCtx; 28 | private List mEnabledFields; 29 | private String[] mSelectionArgs; 30 | private String mSorting; 31 | private String mSelection; 32 | private static final String MAIN_DATA_KEY = "data1"; 33 | private static final String LABEL_DATA_KEY = "data2"; 34 | private static final String CUSTOM_LABEL_DATA_KEY = "data3"; 35 | private static final String ID_KEY = "contact_id"; 36 | private static final String[] WITH_LABEL_PROJECTION = new String[]{ID_KEY, MAIN_DATA_KEY, LABEL_DATA_KEY, CUSTOM_LABEL_DATA_KEY}; 37 | private static final String[] CONTACTS_PROJECTION = new String[]{ContactsContract.Contacts._ID, ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP, 38 | ContactsContract.Contacts.PHOTO_URI, ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts.STARRED}; 39 | private static final String[] CONTACTS_PROJECTION_LOW_API = new String[]{ContactsContract.Contacts._ID, 40 | ContactsContract.Contacts.PHOTO_URI, ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts.STARRED}; 41 | private static final String[] ADDITIONAL_DATA_PROJECTION = new String[]{ContactsContract.Contacts._ID, 42 | ContactsContract.RawContacts.ACCOUNT_TYPE, ContactsContract.RawContacts.ACCOUNT_NAME}; 43 | private Class mContactDataClass; 44 | 45 | ContactsGetter(Context ctx, List enabledFields, String sorting, String[] selectionArgs, String selection) { 46 | this.mCtx = ctx; 47 | this.mResolver = ctx.getContentResolver(); 48 | this.mEnabledFields = enabledFields; 49 | this.mSelectionArgs = selectionArgs; 50 | this.mSorting = sorting; 51 | this.mSelection = selection; 52 | } 53 | 54 | ContactsGetter setContactDataClass(Class mContactDataClass) { 55 | this.mContactDataClass = mContactDataClass; 56 | return this; 57 | } 58 | 59 | private Cursor getContactsCursorWithSelection(String ordering, String selection, String[] selectionArgs) { 60 | return mResolver.query(ContactsContract.Contacts.CONTENT_URI, 61 | android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1 ? CONTACTS_PROJECTION : CONTACTS_PROJECTION_LOW_API, selection, selectionArgs, ordering); 62 | } 63 | 64 | private Cursor getContactsCursorWithAdditionalData() { 65 | return mResolver.query(ContactsContract.RawContacts.CONTENT_URI, ADDITIONAL_DATA_PROJECTION, null, null, null); 66 | } 67 | 68 | private T getContactData() { 69 | if (mContactDataClass == null) { 70 | return (T) new ContactData() { 71 | }; 72 | } 73 | try { 74 | return (T) mContactDataClass.getConstructor().newInstance(); 75 | } catch (Exception e) { 76 | e.printStackTrace(); 77 | } 78 | return null; 79 | } 80 | 81 | List getContacts() { 82 | Cursor mainCursor = getContactsCursorWithSelection(mSorting, mSelection, mSelectionArgs); 83 | Cursor additionalDataCursor = getContactsCursorWithAdditionalData(); 84 | SparseArray contactsSparse = new SparseArray<>(); 85 | List result = new ArrayList<>(); 86 | if (mainCursor == null) 87 | return result; 88 | SparseArray> phonesDataMap = mEnabledFields.contains(FieldType.PHONE_NUMBERS) ? getPhoneNumberMap() : new SparseArray>(); 89 | SparseArray> addressDataMap = mEnabledFields.contains(FieldType.ADDRESS) ? getDataMap(getCursorFromContentType(WITH_LABEL_PROJECTION, StructuredPostal.CONTENT_ITEM_TYPE), new WithLabelCreator
() { 90 | @Override 91 | public Address create(String mainData, int contactId, int labelId, String labelName) { 92 | Address address; 93 | if (labelName != null) 94 | address = new Address(mainData, labelName); 95 | else 96 | address = new Address(mCtx, mainData, labelId); 97 | address.setContactId(contactId); 98 | return address; 99 | } 100 | }) : new SparseArray>(); 101 | SparseArray> emailDataMap = mEnabledFields.contains(FieldType.EMAILS) ? getDataMap(getCursorFromContentType(WITH_LABEL_PROJECTION, CommonDataKinds.Email.CONTENT_ITEM_TYPE), new WithLabelCreator() { 102 | @Override 103 | public Email create(String mainData, int contactId, int labelId, String labelName) { 104 | Email email; 105 | if (labelName != null) 106 | email = new Email(mainData, labelName); 107 | else 108 | email = new Email(mCtx, mainData, labelId); 109 | email.setContactId(contactId); 110 | return email; 111 | } 112 | }) : new SparseArray>(); 113 | SparseArray> specialDateMap = mEnabledFields.contains(FieldType.SPECIAL_DATES) ? getDataMap(getCursorFromContentType(WITH_LABEL_PROJECTION, Event.CONTENT_ITEM_TYPE), new WithLabelCreator() { 114 | @Override 115 | public SpecialDate create(String mainData, int contactId, int labelId, String labelName) { 116 | SpecialDate specialData; 117 | if (labelName != null) 118 | specialData = new SpecialDate(mainData, labelName); 119 | else 120 | specialData = new SpecialDate(mCtx, mainData, labelId); 121 | specialData.setContactId(contactId); 122 | return specialData; 123 | } 124 | }) : new SparseArray>(); 125 | SparseArray> relationMap = mEnabledFields.contains(FieldType.RELATIONS) ? getDataMap(getCursorFromContentType(WITH_LABEL_PROJECTION, CommonDataKinds.Relation.CONTENT_ITEM_TYPE), new WithLabelCreator() { 126 | @Override 127 | public Relation create(String mainData, int contactId, int labelId, String labelName) { 128 | Relation relation; 129 | if (labelName != null) 130 | relation = new Relation(mainData, labelName); 131 | else 132 | relation = new Relation(mCtx, mainData, labelId); 133 | relation.setContactId(contactId); 134 | return relation; 135 | } 136 | }) : new SparseArray>(); 137 | SparseArray> imAddressesDataMap = mEnabledFields.contains(FieldType.IM_ADDRESSES) ? getIMAddressesMap() : new SparseArray>(); 138 | SparseArray> websitesDataMap = mEnabledFields.contains(FieldType.WEBSITES) ? getWebSitesMap() : new SparseArray>(); 139 | SparseArray notesDataMap = mEnabledFields.contains(FieldType.NOTES) ? getStringDataMap(Note.CONTENT_ITEM_TYPE) : new SparseArray(); 140 | SparseArray nicknameDataMap = mEnabledFields.contains(FieldType.NICKNAME) ? getStringDataMap(Nickname.CONTENT_ITEM_TYPE) : new SparseArray(); 141 | SparseArray sipDataMap = mEnabledFields.contains(FieldType.SIP) ? getStringDataMap(SipAddress.CONTENT_ITEM_TYPE) : new SparseArray(); 142 | SparseArray organisationDataMap = mEnabledFields.contains(FieldType.ORGANIZATION) ? getOrganizationDataMap() : new SparseArray(); 143 | SparseArray nameDataMap = mEnabledFields.contains(FieldType.NAME_DATA) ? getNameDataMap() : new SparseArray(); 144 | SparseArray> groupsDataMap = mEnabledFields.contains(FieldType.GROUPS) ? getGroupsDataMap() : new SparseArray>(); 145 | while (mainCursor.moveToNext()) { 146 | int id = mainCursor.getInt(mainCursor.getColumnIndex(ContactsContract.Contacts._ID)); 147 | long date = 0; 148 | if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) 149 | date = mainCursor.getLong(mainCursor.getColumnIndex(ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP)); 150 | String photoUriString = mainCursor.getString(mainCursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI)); 151 | String lookupKey = mainCursor.getString(mainCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)); 152 | boolean isFavorite = mainCursor.getInt(mainCursor.getColumnIndex(ContactsContract.Contacts.STARRED)) == 1; 153 | Uri photoUri = photoUriString == null ? Uri.EMPTY : Uri.parse(photoUriString); 154 | T data = (T) getContactData() 155 | .setContactId(id) 156 | .setLookupKey(lookupKey) 157 | .setLastModificationDate(date) 158 | .setPhoneList(phonesDataMap.get(id)) 159 | .setAddressesList(addressDataMap.get(id)) 160 | .setEmailList(emailDataMap.get(id)) 161 | .setWebsitesList(websitesDataMap.get(id)) 162 | .setNote(notesDataMap.get(id)) 163 | .setImAddressesList(imAddressesDataMap.get(id)) 164 | .setRelationsList(relationMap.get(id)) 165 | .setSpecialDatesList(specialDateMap.get(id)) 166 | .setNickName(nicknameDataMap.get(id)) 167 | .setOrganization(organisationDataMap.get(id)) 168 | .setSipAddress(sipDataMap.get(id)) 169 | .setNameData(nameDataMap.get(id)) 170 | .setPhotoUri(photoUri) 171 | .setFavorite(isFavorite) 172 | .setGroupList(groupsDataMap.get(id)) 173 | .setCompositeName(mainCursor.getString(mainCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))); 174 | contactsSparse.put(id, data); 175 | result.add(data); 176 | } 177 | mainCursor.close(); 178 | while (additionalDataCursor.moveToNext()) { 179 | int id = additionalDataCursor.getInt(additionalDataCursor.getColumnIndex(ContactsContract.RawContacts._ID)); 180 | ContactData relatedContactData = contactsSparse.get(id); 181 | if (relatedContactData != null) { 182 | String accountType = additionalDataCursor.getString(additionalDataCursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE)); 183 | String accountName = additionalDataCursor.getString(additionalDataCursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_NAME)); 184 | relatedContactData.setAccountName(accountName) 185 | .setAccountType(accountType); 186 | } 187 | } 188 | additionalDataCursor.close(); 189 | return result; 190 | } 191 | 192 | 193 | private SparseArray> getWebSitesMap() { 194 | SparseArray> idSiteMap = new SparseArray<>(); 195 | Cursor websiteCur = getCursorFromContentType(new String[]{ID_KEY, MAIN_DATA_KEY}, Website.CONTENT_ITEM_TYPE); 196 | if (websiteCur != null) { 197 | while (websiteCur.moveToNext()) { 198 | int id = websiteCur.getInt(websiteCur.getColumnIndex(ID_KEY)); 199 | String website = websiteCur.getString(websiteCur.getColumnIndex(MAIN_DATA_KEY)); 200 | List currentWebsiteList = idSiteMap.get(id); 201 | if (currentWebsiteList == null) { 202 | currentWebsiteList = new ArrayList<>(); 203 | currentWebsiteList.add(website); 204 | idSiteMap.put(id, currentWebsiteList); 205 | } else currentWebsiteList.add(website); 206 | } 207 | websiteCur.close(); 208 | } 209 | return idSiteMap; 210 | } 211 | 212 | private SparseArray getGroupsMap() { 213 | SparseArray idGroupMap = new SparseArray<>(); 214 | Cursor groupCursor = mResolver.query( 215 | ContactsContract.Groups.CONTENT_URI, 216 | new String[]{ 217 | ContactsContract.Groups._ID, 218 | ContactsContract.Groups.TITLE 219 | }, null, null, null 220 | ); 221 | if (groupCursor != null) { 222 | while (groupCursor.moveToNext()) { 223 | int id = groupCursor.getInt(groupCursor.getColumnIndex(ContactsContract.Groups._ID)); 224 | String title = groupCursor.getString(groupCursor.getColumnIndex(ContactsContract.Groups.TITLE)); 225 | idGroupMap.put(id, new Group() 226 | .setGroupId(id) 227 | .setGroupTitle(title)); 228 | } 229 | groupCursor.close(); 230 | } 231 | return idGroupMap; 232 | } 233 | 234 | private SparseArray> getGroupsDataMap() { 235 | SparseArray> idListGroupMap = new SparseArray<>(); 236 | SparseArray groupMapById = getGroupsMap(); 237 | Cursor groupMembershipCursor = getCursorFromContentType(new String[]{ID_KEY, MAIN_DATA_KEY}, GroupMembership.CONTENT_ITEM_TYPE); 238 | if (groupMembershipCursor != null) { 239 | while (groupMembershipCursor.moveToNext()) { 240 | int id = groupMembershipCursor.getInt(groupMembershipCursor.getColumnIndex(ID_KEY)); 241 | int groupId = groupMembershipCursor.getInt(groupMembershipCursor.getColumnIndex(MAIN_DATA_KEY)); 242 | List currentIdGroupList = idListGroupMap.get(id); 243 | if (currentIdGroupList == null) { 244 | currentIdGroupList = new ArrayList<>(); 245 | currentIdGroupList.add(groupMapById.get(groupId)); 246 | idListGroupMap.put(id, currentIdGroupList); 247 | } else 248 | currentIdGroupList.add(groupMapById.get(groupId)); 249 | } 250 | groupMembershipCursor.close(); 251 | } 252 | return idListGroupMap; 253 | } 254 | 255 | 256 | private SparseArray getNameDataMap() { 257 | Cursor nameCursor = getCursorFromContentType(new String[]{ID_KEY, StructuredName.DISPLAY_NAME, StructuredName.GIVEN_NAME, StructuredName.PHONETIC_MIDDLE_NAME, StructuredName.PHONETIC_FAMILY_NAME, 258 | StructuredName.FAMILY_NAME, StructuredName.PREFIX, StructuredName.MIDDLE_NAME, StructuredName.SUFFIX, StructuredName.PHONETIC_GIVEN_NAME}, StructuredName.CONTENT_ITEM_TYPE); 259 | SparseArray nameDataSparseArray = new SparseArray<>(); 260 | if (nameCursor != null) { 261 | while (nameCursor.moveToNext()) { 262 | int id = nameCursor.getInt(nameCursor.getColumnIndex(ID_KEY)); 263 | if (nameDataSparseArray.get(id) == null) 264 | nameDataSparseArray.put(id, new NameData() 265 | .setFullName(nameCursor.getString(nameCursor.getColumnIndex(StructuredName.DISPLAY_NAME))) 266 | .setFirstName(nameCursor.getString(nameCursor.getColumnIndex(StructuredName.GIVEN_NAME))) 267 | .setSurname(nameCursor.getString(nameCursor.getColumnIndex(StructuredName.FAMILY_NAME))) 268 | .setNamePrefix(nameCursor.getString(nameCursor.getColumnIndex(StructuredName.PREFIX))) 269 | .setMiddleName(nameCursor.getString(nameCursor.getColumnIndex(StructuredName.MIDDLE_NAME))) 270 | .setNameSuffix(nameCursor.getString(nameCursor.getColumnIndex(StructuredName.SUFFIX))) 271 | .setPhoneticFirst(nameCursor.getString(nameCursor.getColumnIndex(StructuredName.PHONETIC_GIVEN_NAME))) 272 | .setPhoneticMiddle(nameCursor.getString(nameCursor.getColumnIndex(StructuredName.PHONETIC_MIDDLE_NAME))) 273 | .setPhoneticLast(nameCursor.getString(nameCursor.getColumnIndex(StructuredName.PHONETIC_FAMILY_NAME))) 274 | ); 275 | } 276 | nameCursor.close(); 277 | } 278 | 279 | 280 | return nameDataSparseArray; 281 | } 282 | 283 | private SparseArray> getIMAddressesMap() { 284 | SparseArray> idImAddressMap = new SparseArray<>(); 285 | Cursor cur = getCursorFromContentType(new String[]{ID_KEY, MAIN_DATA_KEY, Im.PROTOCOL, Im.CUSTOM_PROTOCOL}, Im.CONTENT_ITEM_TYPE); 286 | if (cur != null) { 287 | while (cur.moveToNext()) { 288 | int id = cur.getInt(cur.getColumnIndex(ID_KEY)); 289 | String data = cur.getString(cur.getColumnIndex(MAIN_DATA_KEY)); 290 | int labelId = cur.getInt(cur.getColumnIndex(Im.PROTOCOL)); 291 | String customLabel = cur.getString(cur.getColumnIndex(Im.CUSTOM_PROTOCOL)); 292 | IMAddress current; 293 | if (customLabel == null) 294 | current = new IMAddress(mCtx, data, labelId); 295 | else 296 | current = new IMAddress(data, customLabel); 297 | List currentWebsiteList = idImAddressMap.get(id); 298 | if (currentWebsiteList == null) { 299 | currentWebsiteList = new ArrayList<>(); 300 | currentWebsiteList.add(current); 301 | idImAddressMap.put(id, currentWebsiteList); 302 | } else currentWebsiteList.add(current); 303 | } 304 | cur.close(); 305 | } 306 | return idImAddressMap; 307 | } 308 | 309 | private SparseArray> getPhoneNumberMap() { 310 | Cursor phoneCursor = getCursorFromContentType(new String[]{ID_KEY, MAIN_DATA_KEY, LABEL_DATA_KEY, CUSTOM_LABEL_DATA_KEY, ContactsContract.Data.IS_PRIMARY}, Phone.CONTENT_ITEM_TYPE); 311 | SparseArray> dataSparseArray = new SparseArray<>(); 312 | if (phoneCursor != null) { 313 | while (phoneCursor.moveToNext()) { 314 | int id = phoneCursor.getInt(phoneCursor.getColumnIndex(ID_KEY)); 315 | String data = phoneCursor.getString(phoneCursor.getColumnIndex(MAIN_DATA_KEY)); 316 | int labelId = phoneCursor.getInt(phoneCursor.getColumnIndex(LABEL_DATA_KEY)); 317 | boolean isPrimary = phoneCursor.getInt(phoneCursor.getColumnIndex(ContactsContract.Data.IS_PRIMARY)) == 1; 318 | String customLabel = phoneCursor.getString(phoneCursor.getColumnIndex(CUSTOM_LABEL_DATA_KEY)); 319 | PhoneNumber number; 320 | if (customLabel != null) 321 | number = new PhoneNumber(data, customLabel); 322 | else 323 | number = new PhoneNumber(mCtx, data, labelId); 324 | number.setContactId(id); 325 | number.setPrimary(isPrimary); 326 | List currentDataList = dataSparseArray.get(id); 327 | if (currentDataList == null) { 328 | currentDataList = new ArrayList<>(); 329 | currentDataList.add(number); 330 | dataSparseArray.put(id, currentDataList); 331 | } else currentDataList.add(number); 332 | } 333 | phoneCursor.close(); 334 | } 335 | return dataSparseArray; 336 | } 337 | 338 | private SparseArray getStringDataMap(String contentType) { 339 | SparseArray idNoteMap = new SparseArray<>(); 340 | Cursor noteCur = getCursorFromContentType(new String[]{ID_KEY, MAIN_DATA_KEY}, contentType); 341 | if (noteCur != null) { 342 | while (noteCur.moveToNext()) { 343 | int id = noteCur.getInt(noteCur.getColumnIndex(ID_KEY)); 344 | String note = noteCur.getString(noteCur.getColumnIndex(MAIN_DATA_KEY)); 345 | if (note != null) idNoteMap.put(id, note); 346 | } 347 | noteCur.close(); 348 | } 349 | return idNoteMap; 350 | } 351 | 352 | private SparseArray getOrganizationDataMap() { 353 | SparseArray idOrganizationMap = new SparseArray<>(); 354 | Cursor noteCur = getCursorFromContentType(new String[]{ID_KEY, MAIN_DATA_KEY, TITLE}, CONTENT_ITEM_TYPE); 355 | if (noteCur != null) { 356 | while (noteCur.moveToNext()) { 357 | int id = noteCur.getInt(noteCur.getColumnIndex(ID_KEY)); 358 | String organizationName = noteCur.getString(noteCur.getColumnIndex(MAIN_DATA_KEY)); 359 | String organizationTitle = noteCur.getString(noteCur.getColumnIndex(TITLE)); 360 | idOrganizationMap.put(id, new Organization() 361 | .setName(organizationName) 362 | .setTitle(organizationTitle)); 363 | } 364 | noteCur.close(); 365 | } 366 | return idOrganizationMap; 367 | } 368 | 369 | 370 | private SparseArray> getDataMap(Cursor dataCursor, WithLabelCreator creator) { 371 | SparseArray> dataSparseArray = new SparseArray<>(); 372 | if (dataCursor != null) { 373 | while (dataCursor.moveToNext()) { 374 | int id = dataCursor.getInt(dataCursor.getColumnIndex(ID_KEY)); 375 | String data = dataCursor.getString(dataCursor.getColumnIndex(MAIN_DATA_KEY)); 376 | int labelId = dataCursor.getInt(dataCursor.getColumnIndex(LABEL_DATA_KEY)); 377 | String customLabel = dataCursor.getString(dataCursor.getColumnIndex(CUSTOM_LABEL_DATA_KEY)); 378 | T current = creator.create(data, id, labelId, customLabel); 379 | List currentDataList = dataSparseArray.get(id); 380 | if (currentDataList == null) { 381 | currentDataList = new ArrayList<>(); 382 | currentDataList.add(current); 383 | dataSparseArray.put(id, currentDataList); 384 | } else currentDataList.add(current); 385 | } 386 | dataCursor.close(); 387 | } 388 | return dataSparseArray; 389 | } 390 | 391 | private Cursor getCursorFromContentType(String[] projection, String contentType) { 392 | String orgWhere = ContactsContract.Data.MIMETYPE + " = ?"; 393 | String[] orgWhereParams = new String[]{contentType}; 394 | return mResolver.query(ContactsContract.Data.CONTENT_URI, 395 | projection, orgWhere, orgWhereParams, null); 396 | } 397 | 398 | interface WithLabelCreator { 399 | T create(String mainData, int contactId, int labelId, String labelName); 400 | } 401 | 402 | } -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/main/contactsGetter/ContactsGetterBuilder.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.main.contactsGetter; 2 | 3 | import android.content.Context; 4 | import android.provider.ContactsContract; 5 | import com.tomash.androidcontacts.contactgetter.entity.ContactData; 6 | import com.tomash.androidcontacts.contactgetter.interfaces.BaseFilter; 7 | import com.tomash.androidcontacts.contactgetter.main.FieldType; 8 | import com.tomash.androidcontacts.contactgetter.main.Sorting; 9 | import com.tomash.androidcontacts.contactgetter.utils.FilterUtils; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.Iterator; 14 | import java.util.List; 15 | 16 | public class ContactsGetterBuilder { 17 | private Context mCtx; 18 | private String mSortOrder = Sorting.BY_DISPLAY_NAME_ASC.getSorting(); 19 | private StringBuilder mSelectionBuilder = new StringBuilder(); 20 | private List mParamsList = new ArrayList<>(2); 21 | private List mFilterList = new ArrayList<>(8); 22 | private List mEnabledFields = new ArrayList<>(8); 23 | 24 | public ContactsGetterBuilder(Context ctx) { 25 | mCtx = ctx; 26 | } 27 | 28 | /** 29 | *

30 | * Sets sort order for all contacts 31 | *

32 | *

33 | * Sort types could be found here {@link Sorting} 34 | *

35 | *

36 | * By default is ascending by display name 37 | *

38 | * 39 | * @param sortOrder order to sort 40 | */ 41 | public ContactsGetterBuilder setSortOrder(Sorting sortOrder) { 42 | this.mSortOrder = sortOrder.getSorting(); 43 | return this; 44 | } 45 | 46 | /** 47 | *

48 | * Sets sort order for all contacts 49 | *

50 | *

51 | * Sort types could be found here {@link Sorting} 52 | *

53 | *

54 | * By default is ascending by display name 55 | *

56 | * 57 | * @param sortOrder order to sort 58 | */ 59 | public ContactsGetterBuilder setSortOrder(String sortOrder) { 60 | this.mSortOrder = sortOrder; 61 | return this; 62 | } 63 | 64 | /** 65 | *

66 | * Should get all contacts or contacts only with phones 67 | *

68 | *

69 | * Note : Will automatically query for phone numbers. 70 | *

71 | *

72 | * No need to explicitly add Phone numbers to field list 73 | *

74 | * By default returns all contacts 75 | */ 76 | public ContactsGetterBuilder onlyWithPhones() { 77 | if (mSelectionBuilder.length() != 0) 78 | mSelectionBuilder.append(" AND "); 79 | mSelectionBuilder.append(ContactsContract.CommonDataKinds.Phone.HAS_PHONE_NUMBER) 80 | .append(" = 1"); 81 | addField(FieldType.PHONE_NUMBERS); 82 | return this; 83 | } 84 | 85 | /** 86 | *

87 | * Should get contacts only with photos or not 88 | *

89 | * By default returns all contacts 90 | */ 91 | public ContactsGetterBuilder onlyWithPhotos() { 92 | if (mSelectionBuilder.length() != 0) 93 | mSelectionBuilder.append(" AND "); 94 | mSelectionBuilder.append(ContactsContract.CommonDataKinds.Phone.PHOTO_URI) 95 | .append(" IS NOT NULL"); 96 | return this; 97 | } 98 | 99 | /** 100 | * Searches for contacts with name that contains sequence 101 | * 102 | * @param nameLike sequence to search for 103 | */ 104 | public ContactsGetterBuilder withNameLike(String nameLike) { 105 | if (mSelectionBuilder.length() != 0) 106 | mSelectionBuilder.append(" AND "); 107 | mSelectionBuilder.append(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME) 108 | .append(" LIKE ?"); 109 | mParamsList.add("%" + nameLike + "%"); 110 | return this; 111 | } 112 | 113 | /** 114 | * Searches for contacts with this name 115 | * 116 | * @param name name to search for 117 | */ 118 | public ContactsGetterBuilder withName(String name) { 119 | if (mSelectionBuilder.length() != 0) 120 | mSelectionBuilder.append(" AND "); 121 | mSelectionBuilder.append(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME) 122 | .append(" = ?"); 123 | mParamsList.add(name); 124 | return this; 125 | } 126 | 127 | /** 128 | * Searches for contacts that contains this number sequence 129 | * 130 | * @param number number sequence to search for 131 | */ 132 | public ContactsGetterBuilder withPhoneLike(final String number) { 133 | mFilterList.add(FilterUtils.withPhoneLikeFilter(number)); 134 | return onlyWithPhones(); 135 | } 136 | 137 | /** 138 | * Searches for contacts with this number 139 | * 140 | * @param number number to search for 141 | */ 142 | public ContactsGetterBuilder withPhone(final String number) { 143 | mFilterList.add(FilterUtils.withPhoneFilter(number)); 144 | return onlyWithPhones(); 145 | } 146 | 147 | /** 148 | * Searches for contacts with this email 149 | * Implicitly adds Email field 150 | * 151 | * @param email email to search for 152 | */ 153 | public ContactsGetterBuilder withEmail(final String email) { 154 | addField(FieldType.EMAILS); 155 | mFilterList.add(FilterUtils.withEmailFilter(email)); 156 | return this; 157 | } 158 | 159 | /** 160 | * Searches for contacts that contains sequence 161 | * Implicitly adds Email field 162 | * 163 | * @param sequence sequence to search for 164 | */ 165 | public ContactsGetterBuilder withEmailLike(final String sequence) { 166 | addField(FieldType.EMAILS); 167 | mFilterList.add(FilterUtils.withEmailLikeFilter(sequence)); 168 | return this; 169 | } 170 | 171 | /** 172 | * Searches for contacts with this number 173 | * Implicitly adds Address field 174 | * 175 | * @param number number to search for 176 | */ 177 | public ContactsGetterBuilder withAddress(final String number) { 178 | addField(FieldType.ADDRESS); 179 | mFilterList.add(FilterUtils.withAddressFilter(number)); 180 | return this; 181 | } 182 | 183 | /** 184 | * Searches for addresses that contains this sequence 185 | * Implicitly adds Address field 186 | * 187 | * @param sequence sequence to search for 188 | */ 189 | public ContactsGetterBuilder withAddressLike(final String sequence) { 190 | addField(FieldType.ADDRESS); 191 | mFilterList.add(FilterUtils.withAddressLikeFilter(sequence)); 192 | return this; 193 | } 194 | 195 | 196 | private List applyFilters(List contactList) { 197 | for (BaseFilter filter : mFilterList) { 198 | for (Iterator iterator = contactList.iterator(); iterator.hasNext(); ) { 199 | ContactData contact = iterator.next(); 200 | if (!filter.passedFilter(contact)) 201 | iterator.remove(); 202 | } 203 | } 204 | return contactList; 205 | } 206 | 207 | /** 208 | *

209 | * Applies custom filter to query on contacts list 210 | *

211 | *

212 | * Additional filters and example implementations could be found here {@link FilterUtils} 213 | *

214 | * 215 | * @param filter filter to apply 216 | */ 217 | public ContactsGetterBuilder applyCustomFilter(BaseFilter filter) { 218 | mFilterList.add(filter); 219 | return this; 220 | } 221 | 222 | /** 223 | *

224 | * Enables all fields for query 225 | *

226 | *

227 | * Note : Consider to enable fields you need with {@link #addField(FieldType...)} to increase performance 228 | *

229 | */ 230 | public ContactsGetterBuilder allFields() { 231 | addField(FieldType.values()); 232 | return this; 233 | } 234 | 235 | /** 236 | *

237 | * Enables fields that should be queried 238 | *

239 | *

240 | * Number of fields influence on performance 241 | *

242 | * 243 | * @param fieldType field type you want to add 244 | */ 245 | public ContactsGetterBuilder addField(FieldType... fieldType) { 246 | mEnabledFields.addAll(Arrays.asList(fieldType)); 247 | return this; 248 | } 249 | 250 | private ContactsGetter initGetter() { 251 | ContactsGetter getter; 252 | if (mSelectionBuilder.length() == 0) 253 | getter = new ContactsGetter(mCtx, mEnabledFields, mSortOrder, null, null); 254 | else 255 | getter = new ContactsGetter(mCtx, mEnabledFields, mSortOrder, generateSelectionArgs(), generateSelection()); 256 | return getter; 257 | } 258 | 259 | 260 | /** 261 | * Builds list of contacts 262 | * 263 | * @param T class of object you want to get data 264 | */ 265 | public List buildList(Class T) { 266 | return applyFilters((List) initGetter() 267 | .setContactDataClass(T) 268 | .getContacts()); 269 | } 270 | 271 | /** 272 | * Builds list of contacts 273 | */ 274 | public List buildList() { 275 | return applyFilters(initGetter().getContacts()); 276 | } 277 | 278 | /** 279 | * Gets contact by local id 280 | * 281 | * @param id id to search for 282 | * @return contact with data specified by options or null if no contact with this id 283 | */ 284 | public ContactData getById(int id) { 285 | if (mSelectionBuilder.length() != 0) 286 | mSelectionBuilder.append(" AND "); 287 | mSelectionBuilder.append(ContactsContract.CommonDataKinds.Phone._ID) 288 | .append(" = ?"); 289 | mParamsList.add(String.valueOf(id)); 290 | return firstOrNull(); 291 | } 292 | 293 | /** 294 | * Gets contact by local id 295 | * 296 | * @param id id to search for 297 | * @param T class of object you want to get data 298 | * @return contact with data specified by options or null if no contact with this id 299 | */ 300 | public T getById(int id, Class T) { 301 | if (mSelectionBuilder.length() != 0) 302 | mSelectionBuilder.append(" AND "); 303 | mSelectionBuilder.append(ContactsContract.CommonDataKinds.Phone._ID) 304 | .append(" = ?"); 305 | mParamsList.add(String.valueOf(id)); 306 | return firstOrNull(T); 307 | } 308 | 309 | /** 310 | * Get first contact of null if no contacts with these params 311 | */ 312 | public ContactData firstOrNull() { 313 | List contacts = buildList(); 314 | if (contacts.isEmpty()) 315 | return null; 316 | else 317 | return contacts.get(0); 318 | } 319 | 320 | /** 321 | * Get first contact of null if no contacts with these params 322 | * 323 | * @param T class of object you want to get data 324 | */ 325 | public T firstOrNull(Class T) { 326 | List contacts = buildList(T); 327 | if (contacts.isEmpty()) 328 | return null; 329 | else 330 | return contacts.get(0); 331 | } 332 | 333 | private String generateSelection() { 334 | return mSelectionBuilder.toString(); 335 | } 336 | 337 | private String[] generateSelectionArgs() { 338 | return mParamsList.toArray(new String[mParamsList.size()]); 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/main/contactsSaver/ContactsSaver.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.main.contactsSaver; 2 | 3 | import android.content.*; 4 | import android.graphics.Bitmap; 5 | import android.net.Uri; 6 | import android.provider.ContactsContract; 7 | import com.tomash.androidcontacts.contactgetter.entity.*; 8 | import com.tomash.androidcontacts.contactgetter.interfaces.WithLabel; 9 | 10 | import java.io.ByteArrayInputStream; 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.FileOutputStream; 13 | import java.io.InputStream; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | class ContactsSaver { 18 | private ContentResolver mResolver; 19 | 20 | public ContactsSaver(ContentResolver resolver) { 21 | mResolver = resolver; 22 | } 23 | 24 | public int[] insertContacts(List contactDataList) { 25 | ArrayList cvList = new ArrayList<>(100); 26 | 27 | ContentProviderResult[] results = createContacts(contactDataList); 28 | int[] ids = new int[results.length]; 29 | for (int i = 0; i < results.length; i++) { 30 | int id = Integer.parseInt(results[i].uri.getLastPathSegment()); 31 | generateInsertOperations(cvList, contactDataList.get(i), id); 32 | ids[i] = id; 33 | } 34 | mResolver.bulkInsert(ContactsContract.Data.CONTENT_URI, cvList.toArray(new ContentValues[cvList.size()])); 35 | return ids; 36 | } 37 | 38 | private void generateInsertOperations(List contentValuesList, ContactData contactData, int id) { 39 | for (PhoneNumber number : contactData.getPhoneList()) { 40 | contentValuesList.add(getPhonesCV(number, id)); 41 | } 42 | for (Address address : contactData.getAddressesList()) { 43 | contentValuesList.add(getWithLabelCV(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE, address, id)); 44 | } 45 | for (Email email : contactData.getEmailList()) { 46 | contentValuesList.add(getWithLabelCV(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE, email, id)); 47 | } 48 | for (SpecialDate specialDate : contactData.getSpecialDatesList()) { 49 | contentValuesList.add(getWithLabelCV(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE, specialDate, id)); 50 | } 51 | for (Relation relation : contactData.getRelationsList()) { 52 | contentValuesList.add(getWithLabelCV(ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE, relation, id)); 53 | } 54 | for (String webSite : contactData.getWebsitesList()) { 55 | contentValuesList.add(getStringTypeCV(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE, webSite, id)); 56 | } 57 | for (IMAddress imAddress : contactData.getImAddressesList()) { 58 | contentValuesList.add(getImAddressCV(imAddress, id)); 59 | } 60 | if (!contactData.getNote().isEmpty()) 61 | contentValuesList.add(getStringTypeCV(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE, contactData.getNote(), id)); 62 | if (!contactData.getNickName().isEmpty()) 63 | contentValuesList.add(getStringTypeCV(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE, contactData.getNickName(), id)); 64 | if (!contactData.getSipAddress().isEmpty()) 65 | contentValuesList.add(getStringTypeCV(ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE, contactData.getSipAddress(), id)); 66 | contentValuesList.add(getNameDataCV(contactData, id)); 67 | Organization currentOrganization = contactData.getOrganization(); 68 | if (!currentOrganization.getName().isEmpty() || !currentOrganization.getTitle().isEmpty()) 69 | contentValuesList.add(getOrganizationTypeCV(currentOrganization, id)); 70 | saveUpdatedPhoto(id, contactData); 71 | } 72 | 73 | /** 74 | * Save updated photo for the specified raw-contact. 75 | */ 76 | private void saveUpdatedPhoto(long rawContactId, ContactData contactData) { 77 | try { 78 | InputStream inputStream; 79 | if (contactData.getUpdatedPhotoUri() != null) { 80 | inputStream = mResolver.openInputStream(contactData.getUpdatedPhotoUri()); 81 | contactData.setUpdatedPhotoUri(null); 82 | } else if (contactData.getUpdatedBitmap() != null) { 83 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 84 | contactData.getUpdatedBitmap().compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos); 85 | byte[] bitmapdata = bos.toByteArray(); 86 | inputStream = new ByteArrayInputStream(bitmapdata); 87 | contactData.setUpdatedBitmap(null); 88 | } else { 89 | return; 90 | } 91 | 92 | final Uri outputUri = Uri.withAppendedPath( 93 | ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId), 94 | ContactsContract.RawContacts.DisplayPhoto.CONTENT_DIRECTORY); 95 | 96 | FileOutputStream outputStream; 97 | outputStream = mResolver 98 | .openAssetFileDescriptor(outputUri, "rw").createOutputStream(); 99 | final byte[] buffer = new byte[16 * 1024]; 100 | int length; 101 | while ((length = inputStream.read(buffer)) > 0) 102 | outputStream.write(buffer, 0, length); 103 | outputStream.close(); 104 | inputStream.close(); 105 | } catch (Exception ignored) { 106 | } 107 | } 108 | 109 | private ContentValues getWithLabelCV(String contentType, WithLabel withLabel, int id) { 110 | ContentValues contentValues = new ContentValues(); 111 | contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, id); 112 | contentValues.put(ContactsContract.Data.MIMETYPE, contentType); 113 | contentValues.put(ContactsContract.Data.DATA1, withLabel.getMainData()); 114 | contentValues.put(ContactsContract.Data.DATA2, withLabel.getLabelId()); 115 | if (withLabel.getLabelId() == withLabel.getCustomLabelId()) 116 | contentValues.put(ContactsContract.Data.DATA3, withLabel.getLabelName()); 117 | return contentValues; 118 | } 119 | 120 | private ContentValues getNameDataCV(ContactData contactData, int id) { 121 | NameData current = contactData.getNameData(); 122 | ContentValues contentValues = new ContentValues(); 123 | contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, id); 124 | contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); 125 | contentValues.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, current.getFullName().isEmpty() ? contactData.getCompositeName() : current.getFullName()); 126 | if (!current.getFirstName().isEmpty()) 127 | contentValues.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, current.getFirstName()); 128 | if (!current.getSurname().isEmpty()) 129 | contentValues.put(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, current.getSurname()); 130 | if (!current.getNamePrefix().isEmpty()) 131 | contentValues.put(ContactsContract.CommonDataKinds.StructuredName.PREFIX, current.getNamePrefix()); 132 | if (!current.getNameSuffix().isEmpty()) 133 | contentValues.put(ContactsContract.CommonDataKinds.StructuredName.SUFFIX, current.getNameSuffix()); 134 | if (!current.getMiddleName().isEmpty()) 135 | contentValues.put(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, current.getMiddleName()); 136 | if (!current.getPhoneticFirst().isEmpty()) 137 | contentValues.put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME, current.getPhoneticFirst()); 138 | if (!current.getPhoneticMiddle().isEmpty()) 139 | contentValues.put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME, current.getPhoneticMiddle()); 140 | if (!current.getPhoneticLast().isEmpty()) 141 | contentValues.put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME, current.getPhoneticLast()); 142 | return contentValues; 143 | } 144 | 145 | private ContentValues getImAddressCV(IMAddress imAddress, int id) { 146 | ContentValues contentValues = new ContentValues(); 147 | contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, id); 148 | contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE); 149 | contentValues.put(ContactsContract.Data.DATA1, imAddress.getMainData()); 150 | contentValues.put(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Im.TYPE_HOME); 151 | contentValues.put(ContactsContract.Data.DATA5, imAddress.getLabelId()); 152 | if (imAddress.getLabelId() == imAddress.getCustomLabelId()) 153 | contentValues.put(ContactsContract.Data.DATA6, imAddress.getLabelName()); 154 | return contentValues; 155 | } 156 | 157 | private ContentValues getPhonesCV(PhoneNumber phoneNumber, int id) { 158 | ContentValues contentValues = new ContentValues(); 159 | contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, id); 160 | contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE); 161 | contentValues.put(ContactsContract.Data.DATA1, phoneNumber.getMainData()); 162 | contentValues.put(ContactsContract.Data.DATA2, phoneNumber.getLabelId()); 163 | contentValues.put(ContactsContract.Data.IS_PRIMARY, phoneNumber.isPrimary()); 164 | if (phoneNumber.getLabelId() == phoneNumber.getCustomLabelId()) 165 | contentValues.put(ContactsContract.Data.DATA3, phoneNumber.getLabelName()); 166 | return contentValues; 167 | } 168 | 169 | private ContentValues getStringTypeCV(String contentType, String data, int id) { 170 | ContentValues contentValues = new ContentValues(); 171 | contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, id); 172 | contentValues.put(ContactsContract.Data.MIMETYPE, contentType); 173 | contentValues.put(ContactsContract.Data.DATA1, data); 174 | return contentValues; 175 | } 176 | 177 | private ContentValues getOrganizationTypeCV(Organization organization, int id) { 178 | ContentValues contentValues = new ContentValues(); 179 | contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, id); 180 | contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE); 181 | contentValues.put(ContactsContract.Data.DATA1, organization.getName()); 182 | contentValues.put(ContactsContract.Data.DATA4, organization.getTitle()); 183 | return contentValues; 184 | } 185 | 186 | private ContentProviderResult[] createContacts(List contacts) { 187 | ContentProviderResult[] results = null; 188 | ArrayList op_list = new ArrayList<>(); 189 | for (int i = 0; i < contacts.size(); i++) { 190 | ContactData contactData = contacts.get(i); 191 | op_list.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) 192 | .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, contactData.getAccountType()) 193 | .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, contactData.getAccountName()) 194 | .withValue(ContactsContract.RawContacts.STARRED, contactData.isFavorite() ? 1 : 0) 195 | .build()); 196 | } 197 | try { 198 | results = mResolver.applyBatch(ContactsContract.AUTHORITY, op_list); 199 | } catch (Exception ignored) { 200 | } 201 | return results; 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/main/contactsSaver/ContactsSaverBuilder.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.main.contactsSaver; 2 | 3 | import android.content.Context; 4 | import com.tomash.androidcontacts.contactgetter.entity.ContactData; 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | public class ContactsSaverBuilder { 10 | private Context mCtx; 11 | 12 | public ContactsSaverBuilder(Context mCtx) { 13 | this.mCtx = mCtx; 14 | } 15 | 16 | /** 17 | * Saves to phone database list of contacts 18 | * 19 | * @param contactDataList list of contacts you want to save 20 | * @return array with newly created contacts ids 21 | */ 22 | public int[] saveContactsList(List contactDataList) { 23 | return new ContactsSaver(mCtx.getContentResolver()) 24 | .insertContacts(contactDataList); 25 | } 26 | 27 | /** 28 | * Saves contacts with all data to phone database 29 | * 30 | * @param contactData contact you want to save 31 | * @return newly created contacts id 32 | */ 33 | public int saveContact(ContactData contactData) { 34 | List contactDatas = Collections.singletonList(contactData); 35 | int[] ids = new ContactsSaver(mCtx.getContentResolver()) 36 | .insertContacts(contactDatas); 37 | return ids[0]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/main/contactsUpdater/ContactsUpdater.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.main.contactsUpdater; 2 | 3 | class ContactsUpdater { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /androidcontacts/src/main/java/com/tomash/androidcontacts/contactgetter/utils/FilterUtils.java: -------------------------------------------------------------------------------- 1 | package com.tomash.androidcontacts.contactgetter.utils; 2 | 3 | import com.tomash.androidcontacts.contactgetter.entity.Address; 4 | import com.tomash.androidcontacts.contactgetter.entity.ContactData; 5 | import com.tomash.androidcontacts.contactgetter.entity.Email; 6 | import com.tomash.androidcontacts.contactgetter.entity.PhoneNumber; 7 | import com.tomash.androidcontacts.contactgetter.interfaces.BaseFilter; 8 | import com.tomash.androidcontacts.contactgetter.interfaces.FieldFilter; 9 | import com.tomash.androidcontacts.contactgetter.interfaces.ListFilter; 10 | 11 | import java.util.List; 12 | 13 | public class FilterUtils { 14 | public static BaseFilter withPhoneLikeFilter(final String number) { 15 | return new ListFilter() { 16 | @Override 17 | protected String getFilterPattern() { 18 | return number; 19 | } 20 | 21 | @Override 22 | protected String getFilterData(PhoneNumber data) { 23 | return data.getMainData(); 24 | } 25 | 26 | @Override 27 | protected List getFilterContainer(ContactData contact) { 28 | return contact.getPhoneList(); 29 | } 30 | 31 | @Override 32 | protected boolean getFilterCondition(String data, String pattern) { 33 | return formatNumber(data).contains(pattern); 34 | } 35 | 36 | private String formatNumber(String number) { 37 | return number.replaceAll("[^0-9+]", ""); 38 | } 39 | }; 40 | } 41 | 42 | public static BaseFilter withPhoneFilter(final String number) { 43 | return new ListFilter() { 44 | @Override 45 | protected String getFilterPattern() { 46 | return number; 47 | } 48 | 49 | @Override 50 | protected String getFilterData(PhoneNumber data) { 51 | return data.getMainData(); 52 | } 53 | 54 | @Override 55 | protected List getFilterContainer(ContactData contact) { 56 | return contact.getPhoneList(); 57 | } 58 | 59 | @Override 60 | protected boolean getFilterCondition(String data, String pattern) { 61 | return data.equalsIgnoreCase(pattern); 62 | } 63 | }; 64 | } 65 | 66 | public static BaseFilter withEmailFilter(final String email) { 67 | return new ListFilter() { 68 | @Override 69 | protected String getFilterPattern() { 70 | return email; 71 | } 72 | 73 | @Override 74 | protected String getFilterData(Email data) { 75 | return data.getMainData(); 76 | } 77 | 78 | @Override 79 | protected List getFilterContainer(ContactData contact) { 80 | return contact.getEmailList(); 81 | } 82 | 83 | @Override 84 | protected boolean getFilterCondition(String data, String pattern) { 85 | return data.equalsIgnoreCase(pattern); 86 | } 87 | }; 88 | } 89 | 90 | public static BaseFilter withEmailLikeFilter(final String email) { 91 | return new ListFilter() { 92 | @Override 93 | protected String getFilterPattern() { 94 | return email; 95 | } 96 | 97 | @Override 98 | protected String getFilterData(Email data) { 99 | return data.getMainData(); 100 | } 101 | 102 | @Override 103 | protected List getFilterContainer(ContactData contact) { 104 | return contact.getEmailList(); 105 | } 106 | 107 | @Override 108 | protected boolean getFilterCondition(String data, String pattern) { 109 | return data.toLowerCase().contains(pattern.toLowerCase()); 110 | } 111 | }; 112 | } 113 | 114 | public static BaseFilter withAddressLikeFilter(final String address) { 115 | return new ListFilter() { 116 | @Override 117 | protected String getFilterPattern() { 118 | return address; 119 | } 120 | 121 | @Override 122 | protected String getFilterData(Address data) { 123 | return data.getMainData(); 124 | } 125 | 126 | @Override 127 | protected List
getFilterContainer(ContactData contact) { 128 | return contact.getAddressesList(); 129 | } 130 | 131 | @Override 132 | protected boolean getFilterCondition(String data, String pattern) { 133 | return data.toLowerCase().contains(pattern.toLowerCase()); 134 | } 135 | }; 136 | } 137 | 138 | public static BaseFilter withAddressFilter(final String address) { 139 | return new ListFilter() { 140 | @Override 141 | protected String getFilterPattern() { 142 | return address; 143 | } 144 | 145 | @Override 146 | protected String getFilterData(Address data) { 147 | return data.getMainData(); 148 | } 149 | 150 | @Override 151 | protected List
getFilterContainer(ContactData contact) { 152 | return contact.getAddressesList(); 153 | } 154 | 155 | @Override 156 | protected boolean getFilterCondition(String data, String pattern) { 157 | return data.equalsIgnoreCase(pattern); 158 | } 159 | }; 160 | } 161 | 162 | public static BaseFilter withNoteLike(final String noteLike) { 163 | return new FieldFilter() { 164 | @Override 165 | protected String getFilterPattern() { 166 | return noteLike; 167 | } 168 | 169 | @Override 170 | protected String getFilterData(ContactData data) { 171 | return data.getNote(); 172 | } 173 | 174 | @Override 175 | protected boolean getFilterCondition(String data, String pattern) { 176 | return data.toLowerCase().contains(pattern.toLowerCase()); 177 | } 178 | }; 179 | } 180 | 181 | public static BaseFilter withNote(final String note) { 182 | return new FieldFilter() { 183 | @Override 184 | protected String getFilterPattern() { 185 | return note; 186 | } 187 | 188 | @Override 189 | protected String getFilterData(ContactData data) { 190 | return data.getNote(); 191 | } 192 | 193 | @Override 194 | protected boolean getFilterCondition(String data, String pattern) { 195 | return data.equalsIgnoreCase(pattern); 196 | } 197 | }; 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | apply from: 'deps.gradle' 5 | repositories { 6 | jcenter() 7 | google() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:4.1.0' 11 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin_version" 13 | 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | plugins { 19 | id "com.jfrog.bintray" version "1.7.3" 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | jcenter() 25 | google() 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /deps.gradle: -------------------------------------------------------------------------------- 1 | ext.versions = [ 2 | publishVersion : '1.14.2', 3 | buildCode : 16, 4 | artifact : 'androidcontacts', 5 | compileSdkVersion: 30, 6 | 'kotlin_version' : '1.4.10' 7 | ] -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.useAndroidX=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blainepwnz/AndroidContacts/0dc22ca4db9130e06a6f13a879b65929b5d739fb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Oct 18 22:09:43 EEST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':androidcontacts' 2 | include ':testapp' 3 | -------------------------------------------------------------------------------- /testapp/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /testapp/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | buildToolsVersion "29.0.3" 8 | 9 | defaultConfig { 10 | applicationId "com.tomash.testapp" 11 | minSdkVersion 16 12 | targetSdkVersion 29 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | 31 | } 32 | 33 | dependencies { 34 | implementation fileTree(dir: 'libs', include: ['*.jar']) 35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin_version" 36 | implementation 'androidx.appcompat:appcompat:1.1.0' 37 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 38 | implementation project(":androidcontacts") 39 | } 40 | -------------------------------------------------------------------------------- /testapp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /testapp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /testapp/src/main/java/com/tomash/testapp/DeleteExample.kt: -------------------------------------------------------------------------------- 1 | package com.tomash.testapp 2 | 3 | import android.content.Context 4 | import com.tomash.androidcontacts.contactgetter.entity.ContactData 5 | import com.tomash.androidcontacts.contactgetter.main.contactsDeleter.ContactsDeleter 6 | 7 | class DeleteExample( 8 | val deleter: ContactsDeleter 9 | ) { 10 | 11 | fun create(context: Context) { 12 | val contactsDeleter = ContactsDeleter(context) 13 | } 14 | 15 | fun deleteOneContact(contactData: ContactData) { 16 | //usual delete with no need of callbacks 17 | deleter.deleteContact(contactData) 18 | //full range of callbacks, implement any you need 19 | deleter.deleteContact(contactData) { 20 | onCompleted { } 21 | onFailure { } 22 | onResult { } 23 | doFinally { } 24 | } 25 | } 26 | 27 | fun deleteMultipleContacts(contactDatas: List) { 28 | //usual delete with no need of callbacks 29 | deleter.deleteContacts(contactDatas) 30 | //full range of callbacks, implement any you need 31 | deleter.deleteContacts(contactDatas) { 32 | onCompleted { } 33 | onFailure { } 34 | onResult { } 35 | doFinally { } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /testapp/src/main/java/com/tomash/testapp/JavaDeleteExample.java: -------------------------------------------------------------------------------- 1 | package com.tomash.testapp; 2 | 3 | import android.content.Context; 4 | import com.tomash.androidcontacts.contactgetter.entity.ContactData; 5 | import com.tomash.androidcontacts.contactgetter.main.contactsDeleter.ContactsDeleter; 6 | import kotlin.Unit; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Examples how delete contacts 12 | */ 13 | public class JavaDeleteExample { 14 | private ContactsDeleter contactsDeleter; 15 | 16 | /** 17 | * Example of creation of ContactsDeleter object 18 | */ 19 | private ContactsDeleter createContactsDeleter(Context context) { 20 | return ContactsDeleter.Companion.invoke(context); 21 | } 22 | 23 | /** 24 | * Example of deleting one ContactData 25 | */ 26 | private void deleteOneContact(ContactData contactData) { 27 | contactsDeleter.deleteContact(contactData, contactDataExceptionACResult -> { 28 | contactDataExceptionACResult.onResult(deletedContactData -> { 29 | // do something with successfully deleted contact 30 | return Unit.INSTANCE; 31 | }); 32 | contactDataExceptionACResult.onCompleted(() -> { 33 | // do something when successfully deleted contact 34 | return Unit.INSTANCE; 35 | }); 36 | contactDataExceptionACResult.doFinally(() -> { 37 | // do something in case of success or error 38 | return Unit.INSTANCE; 39 | }); 40 | contactDataExceptionACResult.onFailure(error -> { 41 | // do something in case of error 42 | return Unit.INSTANCE; 43 | }); 44 | return Unit.INSTANCE; 45 | }); 46 | } 47 | 48 | /** 49 | * Example of deleting list of ContactData 50 | */ 51 | private void deleteMultipleContacts(List contactData) { 52 | contactsDeleter.deleteContacts(contactData, contactDataExceptionACResult -> { 53 | contactDataExceptionACResult.onResult(contactDataList -> { 54 | // do something with successfully deleted contacts 55 | return Unit.INSTANCE; 56 | }); 57 | return Unit.INSTANCE; 58 | }); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /testapp/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /testapp/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blainepwnz/AndroidContacts/0dc22ca4db9130e06a6f13a879b65929b5d739fb/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /testapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blainepwnz/AndroidContacts/0dc22ca4db9130e06a6f13a879b65929b5d739fb/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /testapp/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | 7 | -------------------------------------------------------------------------------- /testapp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Test App 3 | 4 | -------------------------------------------------------------------------------- /testapp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | --------------------------------------------------------------------------------