mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-02 14:43:09 +01:00
Move system contact interactions into their own module.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
package org.signal.contactstest
|
||||
|
||||
import android.accounts.AbstractAccountAuthenticator
|
||||
import android.accounts.Account
|
||||
import android.accounts.AccountAuthenticatorResponse
|
||||
import android.accounts.AccountManager
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
|
||||
class AccountAuthenticatorService : Service() {
|
||||
companion object {
|
||||
private var accountAuthenticator: AccountAuthenticatorImpl? = null
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return if (intent.action == AccountManager.ACTION_AUTHENTICATOR_INTENT) {
|
||||
getOrCreateAuthenticator().iBinder
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun getOrCreateAuthenticator(): AccountAuthenticatorImpl {
|
||||
if (accountAuthenticator == null) {
|
||||
accountAuthenticator = AccountAuthenticatorImpl(this)
|
||||
}
|
||||
return accountAuthenticator as AccountAuthenticatorImpl
|
||||
}
|
||||
|
||||
private class AccountAuthenticatorImpl(context: Context) : AbstractAccountAuthenticator(context) {
|
||||
override fun addAccount(response: AccountAuthenticatorResponse, accountType: String, authTokenType: String, requiredFeatures: Array<String>, options: Bundle): Bundle? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun confirmCredentials(response: AccountAuthenticatorResponse, account: Account, options: Bundle): Bundle? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun editProperties(response: AccountAuthenticatorResponse, accountType: String): Bundle? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getAuthToken(response: AccountAuthenticatorResponse, account: Account, authTokenType: String, options: Bundle): Bundle? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getAuthTokenLabel(authTokenType: String): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun hasFeatures(response: AccountAuthenticatorResponse, account: Account, features: Array<String>): Bundle? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun updateCredentials(response: AccountAuthenticatorResponse, account: Account, authTokenType: String, options: Bundle): Bundle? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package org.signal.contactstest
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.ContactsContract
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.signal.contacts.SystemContactsRepository.ContactDetails
|
||||
import org.signal.contacts.SystemContactsRepository.ContactPhoneDetails
|
||||
|
||||
class ContactsActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_contacts)
|
||||
|
||||
val list: RecyclerView = findViewById(R.id.list)
|
||||
val adapter = ContactsAdapter()
|
||||
|
||||
list.layoutManager = LinearLayoutManager(this)
|
||||
list.adapter = adapter
|
||||
|
||||
val viewModel: ContactsViewModel by viewModels()
|
||||
viewModel.contacts.observe(this) { adapter.submitList(it) }
|
||||
}
|
||||
|
||||
private inner class ContactsAdapter : ListAdapter<ContactDetails, ContactViewHolder>(object : DiffUtil.ItemCallback<ContactDetails>() {
|
||||
override fun areItemsTheSame(oldItem: ContactDetails, newItem: ContactDetails): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ContactDetails, newItem: ContactDetails): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactViewHolder {
|
||||
return ContactViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.parent_item, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ContactViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ContactViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val givenName: TextView = itemView.findViewById(R.id.given_name)
|
||||
val familyName: TextView = itemView.findViewById(R.id.family_name)
|
||||
val phoneAdapter: PhoneAdapter = PhoneAdapter()
|
||||
val phoneList: RecyclerView = itemView.findViewById<RecyclerView?>(R.id.phone_list).apply {
|
||||
layoutManager = LinearLayoutManager(itemView.context)
|
||||
adapter = phoneAdapter
|
||||
}
|
||||
|
||||
fun bind(contact: ContactDetails) {
|
||||
givenName.text = "Given Name: ${contact.givenName}"
|
||||
familyName.text = "Family Name: ${contact.familyName}"
|
||||
phoneAdapter.submitList(contact.numbers)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class PhoneAdapter : ListAdapter<ContactPhoneDetails, PhoneViewHolder>(object : DiffUtil.ItemCallback<ContactPhoneDetails>() {
|
||||
override fun areItemsTheSame(oldItem: ContactPhoneDetails, newItem: ContactPhoneDetails): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ContactPhoneDetails, newItem: ContactPhoneDetails): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhoneViewHolder {
|
||||
return PhoneViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.child_item, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PhoneViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
}
|
||||
|
||||
private inner class PhoneViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val photo: ImageView = itemView.findViewById(R.id.contact_photo)
|
||||
val displayName: TextView = itemView.findViewById(R.id.display_name)
|
||||
val number: TextView = itemView.findViewById(R.id.number)
|
||||
val type: TextView = itemView.findViewById(R.id.type)
|
||||
val goButton: View = itemView.findViewById(R.id.go_button)
|
||||
|
||||
fun bind(details: ContactPhoneDetails) {
|
||||
if (details.photoUri != null) {
|
||||
photo.setImageBitmap(BitmapFactory.decodeStream(itemView.context.contentResolver.openInputStream(Uri.parse(details.photoUri))))
|
||||
} else {
|
||||
photo.setImageBitmap(null)
|
||||
}
|
||||
displayName.text = details.displayName
|
||||
number.text = details.number
|
||||
type.text = ContactsContract.CommonDataKinds.Phone.getTypeLabel(itemView.resources, details.type, details.label)
|
||||
goButton.setOnClickListener {
|
||||
startActivity(
|
||||
Intent(Intent.ACTION_VIEW).apply {
|
||||
data = details.contactUri
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.signal.contactstest
|
||||
|
||||
import android.accounts.Account
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import org.signal.contacts.SystemContactsRepository
|
||||
import org.signal.contacts.SystemContactsRepository.ContactDetails
|
||||
import org.signal.contacts.SystemContactsRepository.ContactIterator
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
|
||||
class ContactsViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ContactsViewModel::class.java)
|
||||
}
|
||||
|
||||
private val _contacts: MutableLiveData<List<ContactDetails>> = MutableLiveData()
|
||||
|
||||
val contacts: LiveData<List<ContactDetails>>
|
||||
get() = _contacts
|
||||
|
||||
init {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
val account: Account? = SystemContactsRepository.getOrCreateSystemAccount(
|
||||
context = application,
|
||||
applicationId = BuildConfig.APPLICATION_ID,
|
||||
accountDisplayName = "Test"
|
||||
)
|
||||
|
||||
if (account != null) {
|
||||
val contactList: List<ContactDetails> = SystemContactsRepository.getAllSystemContacts(
|
||||
context = application,
|
||||
rewrites = emptyMap(),
|
||||
e164Formatter = { number -> number }
|
||||
).use { it.toList() }
|
||||
|
||||
_contacts.postValue(contactList)
|
||||
} else {
|
||||
Log.w(TAG, "Failed to create an account!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ContactIterator.toList(): List<ContactDetails> {
|
||||
val list: MutableList<ContactDetails> = mutableListOf()
|
||||
forEach { list += it }
|
||||
return list
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.signal.contactstest
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.signal.core.util.logging.Log
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(MainActivity::class.java)
|
||||
private const val PERMISSION_CODE = 7
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
if (hasPermission(Manifest.permission.READ_CONTACTS) && hasPermission(Manifest.permission.WRITE_CONTACTS)) {
|
||||
Log.i(TAG, "Already have permission.")
|
||||
startActivity(Intent(this, ContactsActivity::class.java))
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
findViewById<Button>(R.id.permission_button).setOnClickListener { v ->
|
||||
requestPermissions(arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS), PERMISSION_CODE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
if (requestCode == PERMISSION_CODE) {
|
||||
if (grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
|
||||
startActivity(Intent(this, ContactsActivity::class.java))
|
||||
finish()
|
||||
} else {
|
||||
Toast.makeText(this, "You must provide permissions to continue.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasPermission(permission: String): Boolean {
|
||||
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user