You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
etesync-android/app/src/main/java/com/etesync/syncadapter/ui/importlocal/LocalContactImportFragment.kt

330 lines
13 KiB

package com.etesync.syncadapter.ui.importlocal
import android.accounts.Account
import android.app.ProgressDialog
import android.content.ContentValues.TAG
import android.content.Context
import android.database.Cursor
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.AsyncTask
import android.os.Bundle
import android.provider.ContactsContract
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import at.bitfire.vcard4android.ContactsStorageException
import com.etesync.syncadapter.R
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.model.CollectionInfo
import com.etesync.syncadapter.resource.LocalAddressBook
import com.etesync.syncadapter.resource.LocalContact
import com.etesync.syncadapter.resource.LocalGroup
import java.util.*
class LocalContactImportFragment : Fragment() {
private lateinit var account: Account
private lateinit var uid: String
private var recyclerView: RecyclerView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_local_contact_import, container, false)
recyclerView = view.findViewById<View>(R.id.recyclerView) as RecyclerView
recyclerView!!.layoutManager = LinearLayoutManager(activity)
recyclerView!!.addItemDecoration(DividerItemDecoration(activity!!))
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
importAccount()
}
protected fun importAccount() {
val provider = context!!.contentResolver.acquireContentProviderClient(ContactsContract.RawContacts.CONTENT_URI)
val cursor: Cursor?
try {
cursor = provider!!.query(ContactsContract.RawContacts.CONTENT_URI,
arrayOf(ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE), null, null,
ContactsContract.RawContacts.ACCOUNT_NAME + " ASC, " + ContactsContract.RawContacts.ACCOUNT_TYPE)
} catch (except: Exception) {
Log.w(TAG, "Addressbook provider is missing columns, continuing anyway")
except.printStackTrace()
return
}
val localAddressBooks = ArrayList<LocalAddressBook>()
var account: Account? = null
val accountNameIndex = cursor!!.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_NAME)
val accountTypeIndex = cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE)
while (cursor.moveToNext()) {
val accountName = cursor.getString(accountNameIndex)
val accountType = cursor.getString(accountTypeIndex)
if (account == null || !(account.name == accountName && account.type == accountType)) {
if (accountName != null && accountType != null) {
account = Account(accountName, accountType)
localAddressBooks.add(LocalAddressBook(context!!, account, provider))
}
}
}
cursor.close()
provider.release()
recyclerView!!.adapter = ImportContactAdapter(context!!, localAddressBooks, object : OnAccountSelected {
override fun accountSelected(index: Int) {
ImportContacts().execute(localAddressBooks[index])
}
})
}
protected inner class ImportContacts : AsyncTask<LocalAddressBook, Int, ResultFragment.ImportResult>() {
private lateinit var progressDialog: ProgressDialog
override fun onPreExecute() {
progressDialog = ProgressDialog(activity)
progressDialog.setTitle(R.string.import_dialog_title)
progressDialog.setMessage(getString(R.string.import_dialog_adding_entries))
progressDialog.setCanceledOnTouchOutside(false)
progressDialog.setCancelable(false)
progressDialog.isIndeterminate = false
progressDialog.setIcon(R.drawable.ic_import_export_black)
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
progressDialog.show()
}
override fun doInBackground(vararg addressBooks: LocalAddressBook): ResultFragment.ImportResult {
return importContacts(addressBooks[0])
}
override fun onProgressUpdate(vararg values: Int?) {
progressDialog.progress = values[0]!!
}
override fun onPostExecute(result: ResultFragment.ImportResult) {
val activity = activity
if (activity == null) {
Logger.log.warning("Activity is null on import.")
return
}
if (progressDialog.isShowing && !activity.isDestroyed && !activity.isFinishing) {
progressDialog.dismiss()
}
onImportResult(result)
}
private fun importContacts(localAddressBook: LocalAddressBook): ResultFragment.ImportResult {
val result = ResultFragment.ImportResult()
try {
val addressBook = LocalAddressBook.findByUid(requireContext(),
requireContext().contentResolver.acquireContentProviderClient(ContactsContract.RawContacts.CONTENT_URI)!!,
account, uid)
?: throw Exception("Could not find address book")
val localContacts = localAddressBook.findAllContacts()
val localGroups = localAddressBook.findAllGroups()
val oldIdToNewId = HashMap<Long, Long>()
val total = localContacts.size + localGroups.size
progressDialog.max = total
result.total = total.toLong()
var progress = 0
for (currentLocalContact in localContacts) {
val contact = currentLocalContact.contact
try {
contact!!
var localContact: LocalContact? = if (contact.uid == null)
null
else
addressBook.findByUid(contact.uid!!) as LocalContact?
if (localContact != null) {
localContact.updateAsDirty(contact)
result.updated++
} else {
localContact = LocalContact(addressBook, contact, contact.uid, null)
localContact.createAsDirty()
result.added++
}
oldIdToNewId[currentLocalContact.id!!] = localContact.id!!
} catch (e: ContactsStorageException) {
e.printStackTrace()
result.e = e
}
publishProgress(++progress)
}
for (currentLocalGroup in localGroups) {
val group = currentLocalGroup.contact
try {
val members = currentLocalGroup.getMembers().map {
oldIdToNewId[it]!!
}
group!!
var localGroup: LocalGroup? = if (group.uid == null)
null
else
addressBook.findByUid(group.uid!!) as LocalGroup?
if (localGroup != null) {
localGroup.updateAsDirty(group, members)
result.updated++
} else {
localGroup = LocalGroup(addressBook, group, group.uid, null)
localGroup.createAsDirty(members)
result.added++
}
} catch (e: ContactsStorageException) {
e.printStackTrace()
result.e = e
}
publishProgress(++progress)
}
} catch (e: Exception) {
result.e = e
}
return result
}
}
fun onImportResult(importResult: ResultFragment.ImportResult) {
val fragment = ResultFragment.newInstance(importResult)
parentFragmentManager.commit(true) {
add(fragment, "importResult")
}
}
class ImportContactAdapter
/**
* Initialize the dataset of the Adapter.
*
* @param addressBooks containing the data to populate views to be used by RecyclerView.
*/
(context: Context, private val mAddressBooks: List<LocalAddressBook>, private val mOnAccountSelected: OnAccountSelected) : RecyclerView.Adapter<ImportContactAdapter.ViewHolder>() {
private val accountResolver: AccountResolver
/**
* Provide a reference to the enumType of views that you are using (custom ViewHolder)
*/
class ViewHolder(v: View, onAccountSelected: OnAccountSelected) : RecyclerView.ViewHolder(v) {
internal val titleTextView: TextView
internal val descTextView: TextView
internal val iconImageView: ImageView
init {
// Define click listener for the ViewHolder's View.
v.setOnClickListener { onAccountSelected.accountSelected(adapterPosition) }
titleTextView = v.findViewById<View>(R.id.title) as TextView
descTextView = v.findViewById<View>(R.id.description) as TextView
iconImageView = v.findViewById<View>(R.id.icon) as ImageView
}
}
init {
accountResolver = AccountResolver(context)
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
// Create a new view.
val v = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.import_content_list_account, viewGroup, false)
return ViewHolder(v, mOnAccountSelected)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
viewHolder.titleTextView.text = mAddressBooks[position].account.name
val accountInfo = accountResolver.resolve(mAddressBooks[position].account.type)
viewHolder.descTextView.text = accountInfo.name
viewHolder.iconImageView.setImageDrawable(accountInfo.icon)
}
override fun getItemCount(): Int {
return mAddressBooks.size
}
companion object {
private val TAG = "ImportContactAdapter"
}
}
interface OnAccountSelected {
fun accountSelected(index: Int)
}
class DividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
private val mDivider: Drawable?
init {
val a = context.obtainStyledAttributes(ATTRS)
mDivider = a.getDrawable(0)
a.recycle()
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
drawVertical(c, parent)
}
fun drawVertical(c: Canvas, parent: RecyclerView) {
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
val childCount = parent.childCount
for (i in 0 until childCount) {
val child = parent.getChildAt(i)
val params = child
.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom = top + mDivider!!.intrinsicHeight
mDivider.setBounds(left, top, right, bottom)
mDivider.draw(c)
}
}
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
outRect.set(0, 0, 0, mDivider!!.intrinsicHeight)
}
companion object {
private val ATTRS = intArrayOf(android.R.attr.listDivider)
}
}
companion object {
fun newInstance(account: Account, uid: String): LocalContactImportFragment {
val ret = LocalContactImportFragment()
ret.account = account
ret.uid = uid
return ret
}
}
}