Skip to content

Commit

Permalink
Closes #10 #111 Document: howto-get-set-remove-contact-raw-contact-photo
Browse files Browse the repository at this point in the history
  • Loading branch information
vestrel00 committed Nov 28, 2021
1 parent 20c8da3 commit f5dcb0e
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 7 deletions.
4 changes: 2 additions & 2 deletions core/src/main/java/contacts/core/entities/Contact.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ sealed class ContactEntity : Entity {

/**
* The uri to the full-sized image of this contact. This full sized image is from the associated
* [RawContact] of the ContactsProvider's choosing.
* This may be the same as the [photoThumbnailUri] if a full sized photo is not available.
* [RawContact] of the ContactsProvider's choosing. This may be the same as the
* [photoThumbnailUri] if a full sized photo is not available.
*
* To get the latest photo as an InputStream/Bytes/Bitmap/BitmapDrawable or set or remove photo,
* use the ContactPhoto extensions.
Expand Down
211 changes: 207 additions & 4 deletions howto/howto-get-set-remove-contact-raw-contact-photo.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,210 @@
# How do I get/set/remove full-sized and thumbnail photos?

TODO
This library provides several ways to interact with Contact and RawContact full-sized and thumbnail
photos.

Note for people looking at this file, the code and documentation within the code is already complete.
I'm in the process of writing these howto pages to provide examples and more explanations on how
to use the APIs provided in this library.
## Contact and RawContact photos

The photo assigned to a Contact is just a reference to a photo assigned to a RawContact. If a
Contact consists of more than one RawContact, only the photo from one of the RawContacts will be
used by the Contact.

Setting/removing the (main) RawContact's photo will in turn change the Contact photo because the
Contact photo is just a reference to the RawContact photo. The inverse is also true.

RawContact photos are retained when linking and unlinking.

> For more info, read [How do I link/unlink Contacts?](/howto/howto-link-unlink-contacts.md)
## Full-sized photos and thumbnails

Each RawContact may be assigned one photo. The thumbnail is just a downsized version of the
full-sized photo. The full-sized photo is typically displayed in a large view, such as in a contact
detail screen. The thumbnail is typically displayed in small views, such as in a contacts list view.

Setting the full-sized photo will automatically set the thumbnail. The Contacts Provider
automatically creates a downsized version of the full-sized photo.

## Getting contact photo

There are several ways to do this.

Using query APIs to get a list of `Contact`s with photo uris,

```kotlin
val contacts = Contacts(context)
.query()
// if you only want to include photo data in the returned Contacts
.include(
Fields.Contact.PhotoUri,
Fields.Contact.PhotoThumbnailUri
)
.find()

for (contact in contacts) {
Log.d(
"Contact",
"""
Photo Uri: ${contact.photoUri}
Thumbnail Uri: ${contact.photoThumbnailUri}
""".trimIndent()
)
}
```

> For more info, read [How do I get a list of contacts in the simplest way?](/howto/howto-query-contacts.md)
> and [How do I get a list of contacts in a more advanced way?](/howto/howto-query-contacts-advanced.md)
Using one of the extension functions in `contacts.core.util.ContactPhoto.kt` to get photo data,

```kotlin
val photoInputStream = contact.photoInputStream(contactsApi)
val photoBytes = contact.photoBytes(contactsApi)
val photoBitmap = contact.photoBitmap(contactsApi)
val photoBitmapDrawable = contact.photoBitmapDrawable(contactsApi)

val photoThumbnailInputStream = contact.photoThumbnailInputStream(contactsApi)
val photoThumbnailBytes = contact.photoThumbnailBytes(contactsApi)
val photoThumbnailBitmap = contact.photoThumbnailBitmap(contactsApi)
val photoThumbnailBitmapDrawable = contact.photoThumbnailBitmapDrawable(contactsApi)
```

To get RawContact photos directly, use one of the extension functions in `contacts.core.util.RawContactPhoto.kt`,

```kotlin
val photoInputStream = rawContact.photoInputStream(contactsApi)
val photoBytes = rawContact.photoBytes(contactsApi)
val photoBitmap = rawContact.photoBitmap(contactsApi)
val photoBitmapDrawable = rawContact.photoBitmapDrawable(contactsApi)

val photoThumbnailInputStream = rawContact.photoThumbnailInputStream(contactsApi)
val photoThumbnailBytes = rawContact.photoThumbnailBytes(contactsApi)
val photoThumbnailBitmap = rawContact.photoThumbnailBitmap(contactsApi)
val photoThumbnailBitmapDrawable = rawContact.photoThumbnailBitmapDrawable(contactsApi)
```

> Keep in mind that the Contact photo is just a reference to one of its RawContact's photo.
## Setting contact photo

Setting the photo can only be done after the Contact or RawContact has been inserted. In other
words, photo management can only be done for existing Contacts/RawContacts.

To set the Contact photo, use one of the extension functions in `contacts.core.util.ContactPhoto.kt`,

```kotlin
contact.setPhoto(contactsApi, photoInputStream)
contact.setPhoto(contactsApi, photoBytes)
contact.setPhoto(contactsApi, photoBitmap)
contact.setPhoto(contactsApi, photoBitmapDrawable)
```

Setting the full-sized photo will automatically set the thumbnail. The Contacts Provider
automatically creates a downsized version of the full-sized photo.

To set a RawContact photo, use one of the extension functions in `contacts.core.util.RawContactPhoto.kt`,

```kotlin
rawContact.setPhoto(contactsApi, photoInputStream)
rawContact.setPhoto(contactsApi, photoBytes)
rawContact.setPhoto(contactsApi, photoBitmap)
rawContact.setPhoto(contactsApi, photoBitmapDrawable)
```

> Keep in mind that the Contact photo is just a reference to one of its RawContact's photo.
## Removing contact photo

To remove the Contact (and corresponding RawContact) photo (full-sized and thumbnail),

```kotlin
contact.removePhoto(contactsApi)
```

To remove a specific RawContact's photo (full-sized and thumbnail),

```kotlin
rawContact.removePhoto(contactsApi)
```

> Keep in mind that the Contact photo is just a reference to one of its RawContact's photo.
## Using the ui PhotoPicker extensions

The `contacts.ui.util.PhotoPicker.kt` in the `ui` module` provides extension functions to make
selecting existing photos, taking new photos, and removing photos easier. It provides you the same
UX as the native Contacts app. To use it,

```kotlin
Activity {
fun onPhotoViewClicked() {
showPhotoPickerDialog(
withRemovePhotoOption = true,
removePhoto = {
contact.removePhoto(contactsApi)
}
)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
onPhotoPicked(requestCode, resultCode, data,
photoBitmapPicked = { photoBitmap ->
contact.setPhoto(contactsApi, photoBitmap)
},
photoUriPicked = { uri ->
// Note that bitmap decoding should be done in a non-UI thread. Threading has been
// left out of this example for brevity.
val photoBitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, uri))
} else {
MediaStore.Images.Media.getBitmap(context.contentResolver, uri)
}

contact.setPhoto(contactsApi, photoBitmap)
}
)
}
}
```

## Performing photo management asynchronously

All of the code shown in this guide are done in the same thread as the call-site. This may result
in a choppy UI.

To perform the work in a different thread, use the Kotlin coroutine extensions provided in the `async` module.
For more info, read [How do I use the async module to simplify executing work outside of the UI thread using coroutines?](/howto/howto-use-api-with-async-execution.md)

You may, of course, use other multi-threading libraries or just do it yourself =)

> Extensions for Kotlin Flow and RxJava are also in the v1 roadmap.
## Performing photo management with permission

Getting and setting photos require the `android.permission.READ_CONTACTS` and
`android.permission.WRITE_CONTACTS` permissions respectively. If not granted, getting/setting photos
will fail.

To perform the get/set/remove photo operations with permission, use the extensions provided in the
`permissions` module. For more info, read [How do I use the permissions module to simplify permission handling using coroutines?](/howto/howto-use-api-with-permissions-handling.md)

You may, of course, use other permission handling libraries or just do it yourself =)

## FAQs

### Issue #110: Can photo be set using a uri instead of bytes and bitmaps?

No and yes. The core APIs provided in this library only provides functions that the Contacts
Provider natively supports. This means setting Contact or RawContact photo only using bytes (and
other similar types). See documentation in `ContactsContract.RawContacts.DisplayPhoto`.

Photos are stored and managed by the Contacts Provider, which in turn provides specific URIs for
RawContacts and Contacts for read/write access to those photos. We cannot simply just pass in our
own URIs. The Contacts Provider will not accept it. The Contacts Provider will only accept raw photo
data. It will then generate and manage URIs on its own automatically to enforce data integrity.

Consumers may write their own functions to convert a URI to a byte array or bitmap using whatever
imaging libraries they want. Certain URIs/URLs may require networking and heavy image processing,
which this **Contacts library** will not cover! URI/URL to image conversion simply does not belong
in this library!
3 changes: 2 additions & 1 deletion sample/src/main/java/contacts/sample/view/PhotoView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ abstract class PhotoView @JvmOverloads constructor(
setOnClickListener {
activity?.let {
isPickingPhoto = true
it.showPhotoPickerDialog(withRemovePhotoOption = photoDrawable != null,
it.showPhotoPickerDialog(
withRemovePhotoOption = photoDrawable != null,
removePhoto = {
photoHasChanged = true
isPickingPhoto = false
Expand Down
3 changes: 3 additions & 0 deletions ui/src/main/java/contacts/ui/util/PhotoPicker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ fun onPhotoPicked(
bitmap?.let(photoBitmapPicked)
}
REQUEST_SELECT_PHOTO -> {
// Note that converting the uri to an image should be done outside of the UI thread.
// This is the reason why this uri is not converted here because we do not want to
// add threading code to this function.
val uri = intent?.data
uri?.let(photoUriPicked)
}
Expand Down

0 comments on commit f5dcb0e

Please sign in to comment.