Skip to content

Commit

Permalink
Merge pull request #200 from vestrel00/25-sim-card-access
Browse files Browse the repository at this point in the history
Implemented SIM card query, insert, update, and delete!
  • Loading branch information
vestrel00 authored Mar 25, 2022
2 parents f5a20df + 6a153b2 commit 09b4f3b
Show file tree
Hide file tree
Showing 43 changed files with 2,453 additions and 63 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ as the native (AOSP) Android Contacts app and Google Contacts app, this library
- 🚂 [Upcoming release - v0.2.0](https://github.com/vestrel00/contacts-android/discussions/146)
- 🗺 [Project roadmap][project-roadmap]
- 💌 [Why use this library?][why-use-this]
- 🆚 [How does this library compare to other Contacts libraries?][compare-other-libs]

## Features

Expand Down Expand Up @@ -64,7 +63,9 @@ The `core` module provides,
-[Query](/docs/customdata/query-custom-data.md), [insert](/docs/customdata/insert-custom-data.md),
[update](/docs/customdata/update-custom-data.md), and [delete](/docs/customdata/delete-custom-data.md) **custom data**.
-[Query](/docs/blockednumbers/query-blocked-numbers.md), [insert](/docs/blockednumbers/insert-blocked-numbers.md),
and [delete](/docs/blockednumbers/delete-blocked-numbers.md) **blocked numbers**.
and [delete](/docs/blockednumbers/delete-blocked-numbers.md) **Blocked Numbers**.
-[Query](/docs/sim/query-sim-contacts.md), [insert](/docs/sim/insert-sim-contacts.md),
[update](/docs/sim/update-sim-contacts.md), and [delete](/docs/sim/delete-sim-contacts.md) **SIM card contacts**.
-[Query](/docs/accounts/query-accounts.md) for Accounts in the system or RawContacts table.
-[Query](/docs/accounts/query-raw-contacts.md) for just RawContacts.
-[Associate **local RawContacts** (no Account) to an Account](/docs/accounts/associate-device-local-raw-contacts-to-an-account.md).
Expand Down Expand Up @@ -105,10 +106,9 @@ Also included are some pre-baked goodies to be used as is or just for reference,

There are also more features that are on the way!

1. ☢️ [SIM card query, insert, update, and delete](https://github.com/vestrel00/contacts-android/issues/26).
2. ☢️ [Work profile contacts](https://github.com/vestrel00/contacts-android/issues/186)
3. ☢️ [Dynamically integrate custom data from other apps](https://github.com/vestrel00/contacts-android/issues/112)
4. ☢️ [Read/write from/to .VCF file](https://github.com/vestrel00/contacts-android/issues/26).
1. ☢️ [Work profile contacts](https://github.com/vestrel00/contacts-android/issues/186)
2. ☢️ [Dynamically integrate custom data from other apps](https://github.com/vestrel00/contacts-android/issues/112)
3. ☢️ [Read/write from/to .VCF file](https://github.com/vestrel00/contacts-android/issues/26).

## Installation

Expand Down Expand Up @@ -422,8 +422,8 @@ return no results.
## Full documentation, guides, and samples

**The above examples barely scratches the surface of what this library provides.** For more in-depth
documentation, visit the [docs](/docs/) or visit the [GitHub Pages][github-pages]. For a sample app
reference, take a look at and run the `sample` module.
documentation, visit the [GitHub Pages][github-pages]. For a sample app reference, take a look at
and run the `sample` module.

## All APIs in the library are optimized!

Expand Down
33 changes: 33 additions & 0 deletions async/src/main/java/contacts/async/sim/SimContactsDeleteAsync.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package contacts.async.sim

import contacts.async.ASYNC_DISPATCHER
import contacts.core.sim.SimContactsDelete
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext

/**
* Suspends the current coroutine, performs the operation in the given [context], then returns the
* result.
*
* Computations automatically stops if the parent coroutine scope / job is cancelled.
*
* See [SimContactsDelete.commit].
*/
suspend fun SimContactsDelete.commitWithContext(
context: CoroutineContext = ASYNC_DISPATCHER
): SimContactsDelete.Result = withContext(context) { commit() }

/**
* Creates a [CoroutineScope] with the given [context], performs the operation in that scope, then
* returns the [Deferred] result.
*
* Computations automatically stops if the parent coroutine scope / job is cancelled.
*
* See [SimContactsDelete.commit].
*/
fun SimContactsDelete.commitAsync(
context: CoroutineContext = ASYNC_DISPATCHER
): Deferred<SimContactsDelete.Result> = CoroutineScope(context).async { commit() }
29 changes: 29 additions & 0 deletions async/src/main/java/contacts/async/sim/SimContactsInsertAsync.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package contacts.async.sim

import contacts.async.ASYNC_DISPATCHER
import contacts.core.sim.SimContactsInsert
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

/**
* Suspends the current coroutine, performs the operation in the given [context], then returns the
* result.
*
* Computations automatically stops if the parent coroutine scope / job is cancelled.
*
* See [SimContactsInsert.commit].
*/
suspend fun SimContactsInsert.commitWithContext(context: CoroutineContext = ASYNC_DISPATCHER):
SimContactsInsert.Result = withContext(context) { commit { !isActive } }


/**
* Creates a [CoroutineScope] with the given [context], performs the operation in that scope, then
* returns the [Deferred] result.
*
* Computations automatically stops if the parent coroutine scope / job is cancelled.
*
* See [SimContactsInsert.commit].
*/
fun SimContactsInsert.commitAsync(context: CoroutineContext = ASYNC_DISPATCHER):
Deferred<SimContactsInsert.Result> = CoroutineScope(context).async { commit { !isActive } }
28 changes: 28 additions & 0 deletions async/src/main/java/contacts/async/sim/SimContactsQueryAsync.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package contacts.async.sim

import contacts.async.ASYNC_DISPATCHER
import contacts.core.sim.SimContactsQuery
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

/**
* Suspends the current coroutine, performs the operation in the given [context], then returns the
* result.
*
* Computations automatically stops if the parent coroutine scope / job is cancelled.
*
* See [SimContactsQuery.find].
*/
suspend fun SimContactsQuery.findWithContext(context: CoroutineContext = ASYNC_DISPATCHER):
SimContactsQuery.Result = withContext(context) { find { !isActive } }

/**
* Creates a [CoroutineScope] with the given [context], performs the operation in that scope, then
* returns the [Deferred] result.
*
* Computations automatically stops if the parent coroutine scope / job is cancelled.
*
* See [SimContactsQuery.find].
*/
fun SimContactsQuery.findAsync(context: CoroutineContext = ASYNC_DISPATCHER):
Deferred<SimContactsQuery.Result> = CoroutineScope(context).async { find { !isActive } }
29 changes: 29 additions & 0 deletions async/src/main/java/contacts/async/sim/SimContactsUpdateAsync.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package contacts.async.sim

import contacts.async.ASYNC_DISPATCHER
import contacts.core.sim.SimContactsUpdate
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

/**
* Suspends the current coroutine, performs the operation in the given [context], then returns the
* result.
*
* Computations automatically stops if the parent coroutine scope / job is cancelled.
*
* See [SimContactsUpdate.commit].
*/
suspend fun SimContactsUpdate.commitWithContext(context: CoroutineContext = ASYNC_DISPATCHER):
SimContactsUpdate.Result = withContext(context) { commit { !isActive } }


/**
* Creates a [CoroutineScope] with the given [context], performs the operation in that scope, then
* returns the [Deferred] result.
*
* Computations automatically stops if the parent coroutine scope / job is cancelled.
*
* See [SimContactsUpdate.commit].
*/
fun SimContactsUpdate.commitAsync(context: CoroutineContext = ASYNC_DISPATCHER):
Deferred<SimContactsUpdate.Result> = CoroutineScope(context).async { commit { !isActive } }
4 changes: 2 additions & 2 deletions core/src/main/java/contacts/core/BroadQuery.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import contacts.core.util.unsafeLazy
* In Kotlin,
*
* ```kotlin
* val contacts : List<Contact> = broadQuery.
* val contacts = broadQuery
* .accounts(account)
* .groups(groups)
* .include { Name.all + Address.all }
Expand All @@ -64,7 +64,7 @@ import contacts.core.util.unsafeLazy
* import static contacts.core.Fields.*;
* import static contacts.core.OrderByKt.*;
*
* val contacts : List<Contact> = broadQuery.
* List<Contact> contacts = broadQuery
* .accounts(account)
* .groups(groups)
* .include(new ArrayList<>() {{
Expand Down
32 changes: 10 additions & 22 deletions core/src/main/java/contacts/core/Contacts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import contacts.core.log.EmptyLogger
import contacts.core.log.Logger
import contacts.core.log.LoggerRegistry
import contacts.core.profile.Profile
import contacts.core.sim.SimContacts

/**
* Provides new [Query], [BroadQuery], [Insert], [Update], [Delete], [Data], [Groups], [Profile],
* [Accounts], and [BlockedNumbers] instances.
* [Accounts], [BlockedNumbers], and [SimContacts] instances.
*
* ## Permissions
*
Expand All @@ -26,30 +27,10 @@ import contacts.core.profile.Profile
* [update], and [delete].
*
* Use [permissions] convenience functions to check for required permissions. The same permissions
* apply to [Data], [Groups], and [Profile].
* apply to [Data], [Groups], [Profile], and [SimContacts].
*
* Use [accountsPermissions] convenience functions to check for required permissions to use the
* [Accounts] API.
*
* ## Data
*
* For data-specific operations, use [data].
*
* ## Groups
*
* For group operations, use [groups].
*
* ## Profile
*
* For user profile operations, use [profile].
*
* ## Accounts
*
* For accounts operations, use [accounts].
*
* ## Blocked numbers
*
* For blocked numbers operations, use [blockedNumbers].
*/
interface Contacts {

Expand Down Expand Up @@ -111,6 +92,11 @@ interface Contacts {
*/
fun blockedNumbers(): BlockedNumbers

/**
* Returns a new [SimContacts] instance.
*/
fun sim(): SimContacts

/**
* Returns a [ContactsPermissions] instance, which provides functions for checking required
* permissions for Contacts Provider operations.
Expand Down Expand Up @@ -229,6 +215,8 @@ private class ContactsImpl(
override fun accounts(isProfile: Boolean) = Accounts(this, isProfile)

override fun blockedNumbers() = BlockedNumbers(this)

override fun sim() = SimContacts(this)
}

// region Shortcuts
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/java/contacts/core/ContactsPermissions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ interface ContactsPermissions {
*/
fun canInsert(): Boolean

/**
* Returns true if [WRITE_PERMISSION] is granted.
*/
fun canInsertToSim(): Boolean

/**
* Returns true if [WRITE_PERMISSION] is granted.
*/
Expand All @@ -48,6 +53,9 @@ private class ContactsPermissionsImpl(
applicationContext.isPermissionGrantedFor(WRITE_PERMISSION)
&& applicationContext.isPermissionGrantedFor(GET_ACCOUNTS_PERMISSION)

override fun canInsertToSim(): Boolean =
applicationContext.isPermissionGrantedFor(WRITE_PERMISSION)

override fun canUpdateDelete(): Boolean =
applicationContext.isPermissionGrantedFor(WRITE_PERMISSION)
}
Expand Down
54 changes: 54 additions & 0 deletions core/src/main/java/contacts/core/Fields.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1341,4 +1341,58 @@ object BlockedNumbersFields : FieldSet<BlockedNumbersField>() {
fun all() = all
}

// endregion

// region SIM Table Fields

data class SimContactsField internal constructor(
override val columnName: String,
override val required: Boolean = false
) : Field()

/**
* Fields for SIM table operations.
*/
// Given that we are not adding include, where, and orderbBy, functions in SIM queries due to
// projection, selection, and order not being supported, there is no need to expose this to
// consumers.
internal object SimContactsFields : FieldSet<SimContactsField>() {

val Id = SimContactsField("_id", required = true)

val Name = SimContactsField("name")

/**
* This is only used for populating the [Name] during insert operations. For some reason,
* using the [Name] when inserting into the SIM card table does not set the name but this does.
*
* I do see it in the com.android.internal.telephony.IccProvider.java
*
* Do not include this in [all]!
*/
val Tag = SimContactsField("tag")

val NewTag = SimContactsField("newTag")

val Number = SimContactsField("number")

val NewNumber = SimContactsField("newNumber")

// Not supporting emails until proper application-level support for it is implemented by Android.
// val Emails = SimContactsField("emails")
// val NewEmails = SimContactsField("newEmails")

override val all by unsafeLazy {
// Only added fields used for queries
setOf(Id, Name, Number)
}

/**
* Same as [all], but as a function. This makes it visible to Java consumers when accessing this
* using the object reference directly.
*/
@JvmStatic
fun all() = all
}

// endregion
2 changes: 1 addition & 1 deletion core/src/main/java/contacts/core/Query.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import contacts.core.util.*
* In Kotlin,
*
* ```kotlin
* val contacts : List<Contact> = query
* val contacts = query
* .accounts(account)
* .include { Name.all + Address.all }
* .where { (Name.DisplayName startsWith "john") and (Email.Address endsWith "gmail") }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,6 @@ private class BlockedNumbersInsertImpl(
override fun blockedNumber(configureBlockedNumber: NewBlockedNumber.() -> Unit) =
blockedNumbers(NewBlockedNumber().apply(configureBlockedNumber))


override fun blockedNumbers(vararg blockedNumbers: NewBlockedNumber) =
blockedNumbers(blockedNumbers.asSequence())

Expand Down
9 changes: 4 additions & 5 deletions core/src/main/java/contacts/core/entities/BlockedNumber.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ sealed interface BlockedNumberEntity : Entity {
*/
val normalizedNumber: String?

// type and label are intentionally excluded as per documentation
override val isBlank: Boolean
get() = propertiesAreAllNullOrBlank(number, normalizedNumber)

Expand Down Expand Up @@ -74,12 +73,12 @@ data class BlockedNumber internal constructor(
isRedacted = true,

number = number?.redact(),
normalizedNumber = normalizedNumber?.redact(),
normalizedNumber = normalizedNumber?.redact()
)
}

/**
* A new immutable [BlockedNumberEntity].
* A new mutable [BlockedNumberEntity].
*/
@Parcelize
data class NewBlockedNumber @JvmOverloads constructor(
Expand All @@ -89,12 +88,12 @@ data class NewBlockedNumber @JvmOverloads constructor(

override val isRedacted: Boolean = false

) : BlockedNumberEntity, NewEntity, ImmutableEntity {
) : BlockedNumberEntity, NewEntity, MutableEntity {

override fun redactedCopy() = copy(
isRedacted = true,

number = number?.redact(),
normalizedNumber = normalizedNumber?.redact(),
normalizedNumber = normalizedNumber?.redact()
)
}
Loading

0 comments on commit 09b4f3b

Please sign in to comment.