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(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() 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() { 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() 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, private val mOnAccountSelected: OnAccountSelected) : RecyclerView.Adapter() { 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(R.id.title) as TextView descTextView = v.findViewById(R.id.description) as TextView iconImageView = v.findViewById(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 } } }