Work on Android contacts

This commit is contained in:
Juha Heinanen
2021-01-23 18:59:37 +02:00
parent c7b08f8c93
commit e570620615
6 changed files with 224 additions and 15 deletions

View File

@ -9,6 +9,7 @@ import java.util.ArrayList
class Contact(var name: String, var uri: String, var color: Int, val id: Long) {
var avatarImage: Bitmap? = null
var androidContact = false
companion object {

View File

@ -2,8 +2,11 @@ package com.tutpro.baresip
import android.Manifest
import android.app.Activity
import android.content.ContentProviderOperation
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
@ -11,6 +14,8 @@ import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds
import android.provider.ContactsContract.Contacts.Data
import android.view.Menu
import android.view.MenuItem
import android.view.View
@ -19,6 +24,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.exifinterface.media.ExifInterface
import com.tutpro.baresip.databinding.ActivityContactBinding
import java.io.ByteArrayOutputStream
import java.io.File
class ContactActivity : AppCompatActivity() {
@ -29,6 +35,7 @@ class ContactActivity : AppCompatActivity() {
private lateinit var cardImageAvatarView: ImageView
private lateinit var nameView: EditText
private lateinit var uriView: EditText
private lateinit var androidCheck: CheckBox
private var newContact = false
private var newAvatar = ""
@ -37,6 +44,7 @@ class ContactActivity : AppCompatActivity() {
private var index = 0
private var color = 0
private var id: Long = 0
private var oldAndroid = false
private val READ_REQUEST_CODE = 42
@ -51,6 +59,7 @@ class ContactActivity : AppCompatActivity() {
cardImageAvatarView = binding.ImageAvatar
nameView = binding.Name
uriView = binding.Uri
androidCheck = binding.Android
newContact = intent.getBooleanExtra("new", false)
@ -70,6 +79,7 @@ class ContactActivity : AppCompatActivity() {
uriView.setText(uri)
}
uOrI = uri
androidCheck.isChecked = false
} else {
index = intent.getIntExtra("index", 0)
val contact = Contact.contacts()[index]
@ -85,8 +95,11 @@ class ContactActivity : AppCompatActivity() {
nameView.setText(name)
uriView.setText(contact.uri)
uOrI = index.toString()
androidCheck.isChecked = contact.androidContact
}
oldAndroid = androidCheck.isChecked
textAvatarView.setOnClickListener { _ ->
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
@ -218,6 +231,7 @@ class ContactActivity : AppCompatActivity() {
return true
} else {
contact = Contact(newName, newUri, color, id)
contact.androidContact = androidCheck.isChecked
Contact.contacts().add(contact)
}
} else {
@ -225,6 +239,7 @@ class ContactActivity : AppCompatActivity() {
contact.uri = newUri
contact.name = newName
contact.color = color
contact.androidContact = androidCheck.isChecked
}
when (newAvatar) {
@ -244,6 +259,21 @@ class ContactActivity : AppCompatActivity() {
Contact.contacts().sortBy { Contact -> Contact.name }
if (Utils.checkPermission(this, Manifest.permission.WRITE_CONTACTS)) {
if (contact.androidContact)
addOrUpdateAndroidContact(this, contact)
else
if (oldAndroid)
deleteAndroidContact(this, contact)
} else {
if (contact.androidContact) {
Utils.requestPermission(this,
"Manifest.permission.READ_CONTACTS,Manifest.permission.WRITE_CONTACTS",
MainActivity.CONTACT_PERMISSION_REQUEST_CODE)
return false
}
}
Contact.save()
BaresipService.activities.remove("contact,$newContact,$uOrI")
@ -277,6 +307,15 @@ class ContactActivity : AppCompatActivity() {
}
fun onClick(v: View) {
when (v) {
binding.AndroidTitle -> {
Utils.alertView(this, getString(R.string.android),
getString(R.string.android_contact_help))
}
}
}
private fun showTextAvatar(name: String, color: Int) {
textAvatarView.visibility = View.VISIBLE
cardAvatarView.visibility = View.GONE
@ -290,21 +329,6 @@ class ContactActivity : AppCompatActivity() {
cardImageAvatarView.setImageBitmap(image)
}
private fun getThumbnailSize(ctx: Context): Int {
var thumbnailSize = 96
if (Utils.checkPermission(this, Manifest.permission.READ_CONTACTS)) {
val c = ctx.contentResolver.query(ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
arrayOf(ContactsContract.DisplayPhoto.THUMBNAIL_MAX_DIM),
null, null, null)
if (c != null && c.moveToFirst())
thumbnailSize = c.getInt(0)
else
Log.e("Baresip", "Could not get THUMBNAIL_MAX_DIM")
c?.close()
}
return thumbnailSize
}
private fun rotateBitmap(bitmap: Bitmap, orientation: Int): Bitmap {
val matrix = Matrix()
when (orientation) {
@ -332,4 +356,157 @@ class ContactActivity : AppCompatActivity() {
bitmap.recycle()
return rotatedBitmap
}
private fun addOrUpdateAndroidContact(ctx: Context, contact: Contact) {
val projection = arrayOf(ContactsContract.Data.CONTACT_ID)
val selection = ContactsContract.Data.MIMETYPE + "='" +
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "' AND " +
CommonDataKinds.StructuredName.DISPLAY_NAME + "='" + contact.name + "'"
val c: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection,
selection, null, null)
if (c != null && c.moveToFirst()) {
updateAndroidContact(ctx, c.getLong(0), contact)
} else {
addAndroidContact(ctx, contact)
}
c?.close()
contact.androidContact = true
}
private fun addAndroidContact(ctx: Context, contact: Contact): Boolean {
val cameraRotationMatrix = Matrix()
val ops = ArrayList<ContentProviderOperation>()
val rawContactInsertIndex = ops.size
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null).build())
ops.add(ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, contact.name)
.build())
ops.add(ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(Data.MIMETYPE, CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE)
.withValue(Data.DATA1, contact.uri.substringAfter(":"))
.build())
if (contact.avatarImage != null) {
cameraRotationMatrix.postRotate(180F)
val rotatedPhoto = Bitmap.createBitmap(contact.avatarImage!!, 0, 0,
contact.avatarImage!!.width, contact.avatarImage!!.height,
cameraRotationMatrix, false);
val photoData: ByteArray? = bitmapToPNGByteArray(rotatedPhoto)
if (photoData != null) {
Log.d("Baresip", "Adding photo")
ops.add(ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(Data.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
.withValue(CommonDataKinds.Photo.PHOTO, photoData)
.build())
}
}
try {
ctx.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
} catch (e: Exception) {
Log.e("Baresip", "Adding of contact ${contact.name} failed")
return false
}
return true
}
private fun updateAndroidContact(ctx: Context, contactId: Long, contact: Contact) {
val ops = ArrayList<ContentProviderOperation>()
val selection = ContactsContract.Data.CONTACT_ID + "='" + contactId.toString() +
"' AND " +
Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "'"
if (contact.avatarImage != null) {
val stream = ByteArrayOutputStream()
contact.avatarImage!!.compress(Bitmap.CompressFormat.PNG, 100, stream)
ops.add(ContentProviderOperation
.newUpdate(ContactsContract.Data.CONTENT_URI)
.withSelection(selection, arrayOf())
.withValue(CommonDataKinds.Photo.PHOTO, stream.toByteArray()).build())
} else {
ops.add(ContentProviderOperation
.newUpdate(ContactsContract.Data.CONTENT_URI)
.withSelection(selection, arrayOf())
.withValue(CommonDataKinds.Photo.PHOTO, null).build())
}
try {
ctx.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
} catch (e: Exception) {
Log.e("Baresip", "Update of contact $contactId failed")
}
val contentValues = ContentValues()
contentValues.put(ContactsContract.Data.DATA1, contact.uri)
val where = ContactsContract.Data.CONTACT_ID + "=?" + " AND " + Data.MIMETYPE + "=?"
val args = arrayOf((contactId).toString(), CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE)
ctx.contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, args)
}
private fun bitmapToPNGByteArray(bitmap: Bitmap): ByteArray? {
val size = bitmap.width * bitmap.height * 4
val out = ByteArrayOutputStream(size)
return try {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
out.flush()
out.close()
out.toByteArray()
} catch (e: Exception) {
Log.w("Baresip", "Unable to serialize photo: $e")
null
}
}
companion object {
fun deleteAndroidContact(ctx: Context, contact: Contact): Int {
return ctx.contentResolver.delete(ContactsContract.RawContacts.CONTENT_URI,
ContactsContract.Contacts.DISPLAY_NAME + "='" + contact.name + "'",
null)
}
fun logAndroidSipContacts(ctx: Context): HashMap<Long, MutableList<String>> {
val contacts = HashMap<Long, MutableList<String>>()
val projection = arrayOf(ContactsContract.Data.CONTACT_ID, ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Data.MIMETYPE, ContactsContract.Data.DATA1,
ContactsContract.Data.PHOTO_THUMBNAIL_URI)
val selection = ContactsContract.Data.MIMETYPE + "='" +
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE + "'"
val cur: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection,
selection, null, null)
while (cur != null && cur.moveToNext()) {
val id = cur.getLong(0);
val name = cur.getString(1) // display name
val mime = cur.getString(2) // type of data
val data = cur.getString(3) // info
val thumb = cur.getString(4) // thumbnail
var kind = "unknown"
when (mime) {
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> kind = "sip"
CommonDataKinds.Photo.CONTENT_ITEM_TYPE -> kind = "thumb"
}
Log.d("Baresip", "got $id, $name, $kind - $data")
var info: MutableList<String>
if (contacts.containsKey(id)) {
info = contacts[id]!!
} else {
info = mutableListOf()
info.add("name = $name")
info.add("thumb = $thumb")
contacts[id] = info
}
info.add("$kind = $data")
}
cur?.close()
return contacts
}
}
}

View File

@ -127,6 +127,8 @@ class ContactListAdapter(private val ctx: Context, private val rows: ArrayList<C
}
}
Contact.contacts().removeAt(position)
if (contact.androidContact)
ContactActivity.deleteAndroidContact(ctx, contact)
Contact.save()
this.notifyDataSetChanged()
}

View File

@ -1780,6 +1780,7 @@ class MainActivity : AppCompatActivity() {
const val BACKUP_PERMISSION_REQUEST_CODE = 1
const val RESTORE_PERMISSION_REQUEST_CODE = 2
const val RECORD_PERMISSION_REQUEST_CODE = 3
const val CONTACT_PERMISSION_REQUEST_CODE = 4
}

View File

@ -77,5 +77,30 @@
android:importantForAutofill="no" >
</EditText>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:orientation="horizontal" >
<TextView
android:id="@+id/AndroidTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_toStartOf="@id/Android"
android:textSize="18sp"
android:onClick="onClick"
android:text="@string/android" >
</TextView>
<CheckBox
android:id="@+id/Android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_gravity="end"
android:checked="false" >
</CheckBox>
</RelativeLayout>
</LinearLayout>

View File

@ -301,6 +301,9 @@
<string name="invalid_contact">Invalid contact name \'%1$s\'</string>
<string name="contact_already_exists">Contact \'%1$s\' already exists.</string>
<string name="invalid_contact_uri">Invalid SIP URI</string>
<string name="android" translatable="false">Android</string>
<string name="android_contact_help">This information is exposed in Android contacts.</string>
<!-- Contacts Activity -->
<string name="contacts">Contacts</string>
<string name="contact_action_question">Do you want to call or send message to \'%1$s\'?</string>