Skip to content
Snippets Groups Projects
Select Git revision
  • 45f56c98a2f18a4d2c92308d807aeabb4d723bb3
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • release/201811
  • release/201808
  • releases/beta1
  • packaging
  • native
  • release-0.2.x
  • 1.0.0
  • 0.2.0
  • 0.1.1
  • 0.1.0
25 results

aboutdialog.ui

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    ConversationFragment.kt 50.14 KiB
    /*
     *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
     *
     *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
     *
     *  This program is free software; you can redistribute it and/or modify
     *  it under the terms of the GNU General Public License as published by
     *  the Free Software Foundation; either version 3 of the License, or
     *  (at your option) any later version.
     *
     *  This program is distributed in the hope that it will be useful,
     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     *  GNU General Public License for more details.
     *
     *  You should have received a copy of the GNU General Public License
     *  along with this program; if not, write to the Free Software
     *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     */
    package cx.ring.fragments
    
    import android.Manifest
    import android.animation.LayoutTransition
    import android.animation.ValueAnimator
    import android.annotation.SuppressLint
    import android.app.Activity
    import android.app.ActivityOptions
    import android.content.*
    import android.content.SharedPreferences.OnSharedPreferenceChangeListener
    import android.content.pm.PackageManager
    import android.graphics.Typeface
    import android.hardware.Camera
    import android.net.Uri
    import android.os.Bundle
    import android.os.Environment
    import android.os.IBinder
    import android.provider.MediaStore
    import android.text.Editable
    import android.text.TextUtils
    import android.text.TextWatcher
    import android.util.Log
    import android.view.*
    import android.view.inputmethod.EditorInfo
    import android.widget.*
    import androidx.appcompat.view.menu.MenuBuilder
    import androidx.appcompat.view.menu.MenuPopupHelper
    import androidx.appcompat.widget.PopupMenu
    import androidx.appcompat.widget.Toolbar
    import androidx.core.view.ViewCompat
    import androidx.core.view.WindowInsetsCompat
    import androidx.core.view.inputmethod.InputContentInfoCompat
    import androidx.recyclerview.widget.DefaultItemAnimator
    import androidx.recyclerview.widget.LinearLayoutManager
    import androidx.recyclerview.widget.RecyclerView
    import com.google.android.material.snackbar.Snackbar
    import cx.ring.R
    import cx.ring.adapters.ConversationAdapter
    import cx.ring.application.JamiApplication
    import cx.ring.client.CallActivity
    import cx.ring.client.ContactDetailsActivity
    import cx.ring.client.ConversationActivity
    import cx.ring.client.HomeActivity
    import cx.ring.databinding.FragConversationBinding
    import cx.ring.interfaces.Colorable
    import cx.ring.mvp.BaseSupportFragment
    import cx.ring.service.DRingService
    import cx.ring.service.LocationSharingService
    import cx.ring.services.NotificationServiceImpl
    import cx.ring.services.SharedPreferencesServiceImpl.Companion.getConversationPreferences
    import cx.ring.utils.ActionHelper
    import cx.ring.utils.AndroidFileUtils.copyFileToUri
    import cx.ring.utils.AndroidFileUtils.createAudioFile
    import cx.ring.utils.AndroidFileUtils.createImageFile
    import cx.ring.utils.AndroidFileUtils.createVideoFile
    import cx.ring.utils.AndroidFileUtils.getCacheFile
    import cx.ring.utils.AndroidFileUtils.getMimeTypeFromExtension
    import cx.ring.utils.AndroidFileUtils.getSpaceLeft
    import cx.ring.utils.ContentUriHandler
    import cx.ring.utils.ConversationPath
    import cx.ring.utils.DeviceUtils.isTablet
    import cx.ring.utils.MediaButtonsHelper.MediaButtonsHelperCallback
    import cx.ring.views.AvatarDrawable
    import cx.ring.views.AvatarFactory
    import dagger.hilt.android.AndroidEntryPoint
    import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
    import io.reactivex.rxjava3.core.Completable
    import io.reactivex.rxjava3.core.Single
    import io.reactivex.rxjava3.disposables.CompositeDisposable
    import net.jami.conversation.ConversationPresenter
    import net.jami.conversation.ConversationView
    import net.jami.daemon.JamiService
    import net.jami.model.*
    import net.jami.model.Account.ComposingStatus
    import net.jami.services.NotificationService
    import java.io.File
    import java.util.*
    
    @AndroidEntryPoint
    class ConversationFragment : BaseSupportFragment<ConversationPresenter, ConversationView>(),
        MediaButtonsHelperCallback, ConversationView, OnSharedPreferenceChangeListener {
        private var locationServiceConnection: ServiceConnection? = null
        private var binding: FragConversationBinding? = null
        private var mAudioCallBtn: MenuItem? = null
        private var mVideoCallBtn: MenuItem? = null
        private var currentBottomView: View? = null
        private var mAdapter: ConversationAdapter? = null
        private var marginPx = 0
        private var marginPxTotal = 0
        private val animation = ValueAnimator()
        private var mPreferences: SharedPreferences? = null
        private var mCurrentPhoto: File? = null
        private var mCurrentFileAbsolutePath: String? = null
        private val mCompositeDisposable = CompositeDisposable()
        private var mSelectedPosition = 0
        private var mIsBubble = false
        private var mConversationAvatar: AvatarDrawable? = null
        private val mParticipantAvatars: MutableMap<String, AvatarDrawable> = HashMap()
        private val mSmallParticipantAvatars: MutableMap<String, AvatarDrawable> = HashMap()
        private var mapWidth = 0
        private var mapHeight = 0
        private var mLastRead: String? = null
        private var loading = true
    
        fun getConversationAvatar(uri: String): AvatarDrawable? {
            return mParticipantAvatars[uri]
        }
    
        fun getSmallConversationAvatar(uri: String): AvatarDrawable? {
            synchronized(mSmallParticipantAvatars) { return mSmallParticipantAvatars[uri] }
        }
    
        override fun refreshView(conversation: List<Interaction>) {
            if (binding != null) binding!!.pbLoading.visibility = View.GONE
            mAdapter?.let { adapter ->
                adapter.updateDataset(conversation)
                loading = false
            }
            requireActivity().invalidateOptionsMenu()
        }
    
        override fun scrollToEnd() {
            if (mAdapter!!.itemCount > 0) {
                binding!!.histList.scrollToPosition(mAdapter!!.itemCount - 1)
            }
        }
    
        private fun updateListPadding() {
            if (currentBottomView != null && currentBottomView!!.height != 0) {
                val bottomViewHeight = if (currentBottomView != null) currentBottomView!!.height else 0
                setBottomPadding(binding!!.histList, bottomViewHeight + marginPxTotal)
                val params = binding!!.mapCard.layoutParams as RelativeLayout.LayoutParams
                params.bottomMargin = bottomViewHeight + marginPxTotal
                binding!!.mapCard.layoutParams = params
            }
        }
    
        override fun displayErrorToast(error: Error) {
            val errorString: String = when (error) {
                Error.NO_INPUT -> getString(R.string.call_error_no_camera_no_microphone)
                Error.INVALID_FILE -> getString(R.string.invalid_file)
                Error.NOT_ABLE_TO_WRITE_FILE -> getString(R.string.not_able_to_write_file)
                Error.NO_SPACE_LEFT -> getString(R.string.no_space_left_on_device)
                else -> getString(R.string.generic_error)
            }
            Toast.makeText(requireContext(), errorString, Toast.LENGTH_LONG).show()
        }
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
            val res = resources
            marginPx = res.getDimensionPixelSize(R.dimen.conversation_message_input_margin)
            mapWidth = res.getDimensionPixelSize(R.dimen.location_sharing_minmap_width)
            mapHeight = res.getDimensionPixelSize(R.dimen.location_sharing_minmap_height)
            marginPxTotal = marginPx
            return FragConversationBinding.inflate(inflater, container, false).let { binding ->
                this@ConversationFragment.binding = binding
                binding.presenter = this@ConversationFragment
                animation.duration = 150
                animation.addUpdateListener { valueAnimator: ValueAnimator -> setBottomPadding(binding.histList, valueAnimator.animatedValue as Int) }
    
                ViewCompat.setOnApplyWindowInsetsListener(binding.histList) { _, insets: WindowInsetsCompat ->
                    marginPxTotal = marginPx + insets.systemWindowInsetBottom
                    updateListPadding()
                    insets.consumeSystemWindowInsets()
                    insets
                }
                val layout: View = binding.conversationLayout
    
                // remove action bar height for tablet layout
                if (isTablet(layout.context)) {
                    layout.setPadding(layout.paddingLeft, 0, layout.paddingRight, layout.paddingBottom)
                }
                val paddingTop = layout.paddingTop
                ViewCompat.setOnApplyWindowInsetsListener(layout) { v: View, insets: WindowInsetsCompat ->
                    v.setPadding(v.paddingLeft, paddingTop + insets.systemWindowInsetTop, v.paddingRight, v.paddingBottom)
                    insets.consumeSystemWindowInsets()
                    insets
                }
                binding.ongoingcallPane.visibility = View.GONE
                binding.msgInputTxt.setMediaListener { contentInfo: InputContentInfoCompat ->
                    startFileSend(getCacheFile(requireContext(), contentInfo.contentUri)
                            .flatMapCompletable { file: File -> sendFile(file) }
                            .doFinally { contentInfo.releasePermission() })
                }
                binding.msgInputTxt.setOnEditorActionListener { _, actionId: Int, _ -> actionSendMsgText(actionId) }
                binding.msgInputTxt.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus: Boolean ->
                    if (hasFocus) {
                        val fragment = childFragmentManager.findFragmentById(R.id.mapLayout)
                        if (fragment != null) {
                            (fragment as LocationSharingFragment).hideControls()
                        }
                    }
                }
                binding.msgInputTxt.addTextChangedListener(object : TextWatcher {
                    override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
                    override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
                    override fun afterTextChanged(s: Editable) {
                        val message = s.toString()
                        val hasMessage = !TextUtils.isEmpty(message)
                        presenter.onComposingChanged(hasMessage)
                        if (hasMessage) {
                            binding.msgSend.visibility = View.VISIBLE
                            binding.emojiSend.visibility = View.GONE
                        } else {
                            binding.msgSend.visibility = View.GONE
                            binding.emojiSend.visibility = View.VISIBLE
                        }
                        mPreferences?.let { preferences ->
                            if (hasMessage)
                                preferences.edit().putString(KEY_PREFERENCE_PENDING_MESSAGE, message).apply()
                            else
                                preferences.edit().remove(KEY_PREFERENCE_PENDING_MESSAGE).apply()
                        }
                    }
                })
                setHasOptionsMenu(true)
                binding.root
            }
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            binding?.let { binding ->
                mPreferences?.let { preferences ->
                    val pendingMessage = preferences.getString(KEY_PREFERENCE_PENDING_MESSAGE, null)
                    if (pendingMessage != null && pendingMessage.isNotEmpty()) {
                        binding.msgInputTxt.setText(pendingMessage)
                        binding.msgSend.visibility = View.VISIBLE
                        binding.emojiSend.visibility = View.GONE
                    }
                }
                binding.msgInputTxt.addOnLayoutChangeListener { _, _, _, _, _, oldLeft, oldTop, oldRight, oldBottom ->
                    if (oldBottom == 0 && oldTop == 0) {
                        updateListPadding()
                    } else {
                        if (animation.isStarted) animation.cancel()
                        animation.setIntValues(
                            binding.histList.paddingBottom,
                            (if (currentBottomView == null) 0 else currentBottomView!!.height) + marginPxTotal
                        )
                        animation.start()
                    }
                }
                binding.histList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
                    // The minimum amount of items to have below current scroll position
                    // before loading more.
                    val visibleThreshold = 3
                    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {}
                    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                        val layoutManager = recyclerView.layoutManager as LinearLayoutManager?
                        if (!loading && layoutManager!!.findFirstVisibleItemPosition() < visibleThreshold) {
                            loading = true
                            presenter.loadMore()
                        }
                    }
                })
                val animator = binding.histList.itemAnimator as DefaultItemAnimator?
                animator?.supportsChangeAnimations = false
                binding.histList.adapter = mAdapter
            }
        }
    
        override fun setConversationColor(color: Int) {
            val activity = activity as Colorable?
            activity?.setColor(color)
            mAdapter?.setPrimaryColor(color)
        }
    
        override fun setConversationSymbol(symbol: CharSequence) {
            binding?.emojiSend?.text = symbol
        }
    
        override fun onDestroyView() {
            mPreferences?.unregisterOnSharedPreferenceChangeListener(this)
            animation.removeAllUpdateListeners()
            binding?.histList?.adapter = null
            mCompositeDisposable.clear()
            locationServiceConnection?.let {
                try {
                    requireContext().unbindService(it)
                } catch (e: Exception) {
                    Log.w(TAG, "Error unbinding service: " + e.message)
                }
            }
            mAdapter = null
            super.onDestroyView()
            binding = null
        }
    
        override fun onContextItemSelected(item: MenuItem): Boolean {
            return if (mAdapter!!.onContextItemSelected(item)) true
            else super.onContextItemSelected(item)
        }
    
        fun updateAdapterItem() {
            if (mSelectedPosition != -1) {
                mAdapter!!.notifyItemChanged(mSelectedPosition)
                mSelectedPosition = -1
            }
        }
    
        fun sendMessageText() {
            val message = binding!!.msgInputTxt.text.toString()
            clearMsgEdit()
            presenter.sendTextMessage(message)
        }
    
        fun sendEmoji() {
            presenter.sendTextMessage(binding!!.emojiSend.text.toString())
        }
    
        @SuppressLint("RestrictedApi")
        fun expandMenu(v: View) {
            val context = requireContext()
            val popup = PopupMenu(context, v)
            popup.inflate(R.menu.conversation_share_actions)
            popup.setOnMenuItemClickListener { item: MenuItem ->
                when (item.itemId) {
                    R.id.conv_send_audio -> sendAudioMessage()
                    R.id.conv_send_video -> sendVideoMessage()
                    R.id.conv_send_file -> presenter.selectFile()
                    R.id.conv_share_location -> shareLocation()
                    R.id.chat_plugins -> presenter.showPluginListHandlers()
                }
                false
            }
            popup.menu.findItem(R.id.chat_plugins).isVisible = JamiService.getPluginsEnabled() && !JamiService.getChatHandlers().isEmpty()
            val menuHelper = MenuPopupHelper(context, (popup.menu as MenuBuilder), v)
            menuHelper.setForceShowIcon(true)
            menuHelper.show()
        }
    
        override fun showPluginListHandlers(accountId: String, contactId: String) {
            Log.w(TAG, "show Plugin Chat Handlers List")
            val fragment = PluginHandlersListFragment.newInstance(accountId, contactId)
            childFragmentManager.beginTransaction()
                .add(R.id.pluginListHandlers, fragment, PluginHandlersListFragment.TAG)
                .commit()
            binding?.let { binding ->
                val params = binding.mapCard.layoutParams as RelativeLayout.LayoutParams
                if (params.width != ViewGroup.LayoutParams.MATCH_PARENT) {
                    params.width = ViewGroup.LayoutParams.MATCH_PARENT
                    params.height = ViewGroup.LayoutParams.MATCH_PARENT
                    binding.mapCard.layoutParams = params
                }
                binding.mapCard.visibility = View.VISIBLE
            }
        }
    
        fun hidePluginListHandlers() {
            if (binding!!.mapCard.visibility != View.GONE) {
                binding!!.mapCard.visibility = View.GONE
                val fragmentManager = childFragmentManager
                val fragment = fragmentManager.findFragmentById(R.id.pluginListHandlers)
                if (fragment != null) {
                    fragmentManager.beginTransaction()
                        .remove(fragment)
                        .commit()
                }
            }
            val params = binding!!.mapCard.layoutParams as RelativeLayout.LayoutParams
            if (params.width != mapWidth) {
                params.width = mapWidth
                params.height = mapHeight
                binding!!.mapCard.layoutParams = params
            }
        }
    
        private fun shareLocation() {
            presenter.shareLocation()
        }
    
        fun closeLocationSharing(isSharing: Boolean) {
            val params = binding!!.mapCard.layoutParams as RelativeLayout.LayoutParams
            if (params.width != mapWidth) {
                params.width = mapWidth
                params.height = mapHeight
                binding!!.mapCard.layoutParams = params
            }
            if (!isSharing) hideMap()
        }
    
        fun openLocationSharing() {
            binding!!.conversationLayout.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
            val params = binding!!.mapCard.layoutParams as RelativeLayout.LayoutParams
            if (params.width != ViewGroup.LayoutParams.MATCH_PARENT) {
                params.width = ViewGroup.LayoutParams.MATCH_PARENT
                params.height = ViewGroup.LayoutParams.MATCH_PARENT
                binding!!.mapCard.layoutParams = params
            }
        }
    
        override fun startShareLocation(accountId: String, conversationId: String) {
            showMap(accountId, conversationId, true)
        }
    
        /**
         * Used to update with the past adapter position when a long click was registered
         */
        fun updatePosition(position: Int) {
            mSelectedPosition = position
        }
    
        override fun showMap(accountId: String, contactId: String, open: Boolean) {
            if (binding!!.mapCard.visibility == View.GONE) {
                Log.w(TAG, "showMap $accountId $contactId")
                val fragmentManager = childFragmentManager
                val fragment = LocationSharingFragment.newInstance(accountId, contactId, open)
                fragmentManager.beginTransaction()
                    .add(R.id.mapLayout, fragment, "map")
                    .commit()
                binding!!.mapCard.visibility = View.VISIBLE
            }
            if (open) {
                val fragment = childFragmentManager.findFragmentById(R.id.mapLayout)
                if (fragment != null) {
                    (fragment as LocationSharingFragment).showControls()
                }
            }
        }
    
        override fun hideMap() {
            if (binding!!.mapCard.visibility != View.GONE) {
                binding!!.mapCard.visibility = View.GONE
                val fragmentManager = childFragmentManager
                val fragment = fragmentManager.findFragmentById(R.id.mapLayout)
                if (fragment != null) {
                    fragmentManager.beginTransaction()
                        .remove(fragment)
                        .commit()
                }
            }
        }
    
        private fun sendAudioMessage() {
            if (!presenter.deviceRuntimeService.hasAudioPermission()) {
                requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), REQUEST_CODE_CAPTURE_AUDIO)
            } else {
                try {
                    val ctx = requireContext()
                    val intent = Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
                    mCurrentPhoto = createAudioFile(ctx)
                    startActivityForResult(intent, REQUEST_CODE_CAPTURE_AUDIO)
                } catch (ex: Exception) {
                    Log.e(TAG, "sendAudioMessage: error", ex)
                    Toast.makeText(activity, "Can't find audio recorder app", Toast.LENGTH_SHORT).show()
                }
            }
        }
    
        private fun sendVideoMessage() {
            if (!presenter.deviceRuntimeService.hasVideoPermission()) {
                requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CODE_CAPTURE_VIDEO)
            } else {
                try {
                    val context = requireContext()
                    val intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE).apply {
                        putExtra("android.intent.extras.CAMERA_FACING", Camera.CameraInfo.CAMERA_FACING_FRONT)
                        putExtra("android.intent.extras.LENS_FACING_FRONT", 1)
                        putExtra("android.intent.extra.USE_FRONT_CAMERA", true)
                        putExtra(MediaStore.EXTRA_OUTPUT, ContentUriHandler.getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, createVideoFile(context).apply {
                            mCurrentPhoto = this
                        }))
                    }
                    startActivityForResult(intent, REQUEST_CODE_CAPTURE_VIDEO)
                } catch (ex: Exception) {
                    Log.e(TAG, "sendVideoMessage: error", ex)
                    Toast.makeText(activity, "Can't find video recorder app", Toast.LENGTH_SHORT).show()
                }
            }
        }
    
        fun takePicture() {
            if (!presenter.deviceRuntimeService.hasVideoPermission()) {
                requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CODE_TAKE_PICTURE)
                return
            }
            val c = context ?: return
            try {
                val photoFile = createImageFile(c)
                Log.i(TAG, "takePicture: trying to save to $photoFile")
                val photoURI = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, photoFile)
                val takePictureIntent =
                    Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                        .putExtra("android.intent.extras.CAMERA_FACING", 1)
                        .putExtra("android.intent.extras.LENS_FACING_FRONT", 1)
                        .putExtra("android.intent.extra.USE_FRONT_CAMERA", true)
                mCurrentPhoto = photoFile
                startActivityForResult(takePictureIntent, REQUEST_CODE_TAKE_PICTURE)
            } catch (e: Exception) {
                Toast.makeText(c, "Error taking picture: " + e.localizedMessage, Toast.LENGTH_SHORT)
                    .show()
            }
        }
    
        override fun askWriteExternalStoragePermission() {
            requestPermissions(
                arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                JamiApplication.PERMISSIONS_REQUEST
            )
        }
    
        override fun openFilePicker() {
            val intent = Intent(Intent.ACTION_GET_CONTENT)
            intent.addCategory(Intent.CATEGORY_OPENABLE)
            intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
            intent.type = "*/*"
            startActivityForResult(intent, REQUEST_CODE_FILE_PICKER)
        }
    
        private fun sendFile(file: File): Completable {
            return Completable.fromAction { presenter.sendFile(file) }
        }
    
        private fun startFileSend(op: Completable) {
            setLoading(true)
            op.observeOn(AndroidSchedulers.mainThread())
                .doFinally { setLoading(false) }
                .subscribe({}) { e ->
                    Log.e(TAG, "startFileSend: not able to create cache file", e)
                    displayErrorToast(Error.INVALID_FILE)
                }
        }
    
        override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
            Log.w(TAG, "onActivityResult: $requestCode $resultCode $resultData")
            if (requestCode == REQUEST_CODE_FILE_PICKER) {
                if (resultCode == Activity.RESULT_OK && resultData != null) {
                    val clipData = resultData.clipData
                    if (clipData != null) { // checking multiple selection or not
                        val fNb = clipData.itemCount
                        for (i in 0 until fNb) {
                            val uri = clipData.getItemAt(i).uri
                            startFileSend(getCacheFile(requireContext(), uri)
                                .observeOn(AndroidSchedulers.mainThread())
                                .flatMapCompletable { file: File -> sendFile(file) })
                        }
                    } else {
                        resultData.data?.let { uri ->
                            startFileSend(getCacheFile(requireContext(), uri)
                                .observeOn(AndroidSchedulers.mainThread())
                                .flatMapCompletable { file: File -> sendFile(file) })
                        }
                    }
                }
            } else if (requestCode == REQUEST_CODE_TAKE_PICTURE || requestCode == REQUEST_CODE_CAPTURE_AUDIO || requestCode == REQUEST_CODE_CAPTURE_VIDEO) {
                if (resultCode != Activity.RESULT_OK) {
                    mCurrentPhoto = null
                    return
                }
                val currentPhoto = mCurrentPhoto
                var file: Single<File>? = null
                if (currentPhoto == null || !currentPhoto.exists() || currentPhoto.length() == 0L) {
                    resultData?.data?.let { uri ->
                        file = getCacheFile(requireContext(), uri)
                    }
                } else {
                    file = Single.just(currentPhoto)
                }
                mCurrentPhoto = null
                val sendingFile = file
                if (sendingFile != null)
                    startFileSend(sendingFile.flatMapCompletable { f -> sendFile(f) })
                else
                    Toast.makeText(activity, "Can't find picture", Toast.LENGTH_SHORT).show()
            } else if (requestCode == REQUEST_CODE_SAVE_FILE) {
                val uri = resultData?.data
                if (resultCode == Activity.RESULT_OK && uri != null) {
                    writeToFile(uri)
                }
            }
        }
    
        private fun writeToFile(data: Uri) {
            val path = mCurrentFileAbsolutePath ?: return
            val cr = context?.contentResolver ?: return
            val input = File(path)
            mCompositeDisposable.add(
                copyFileToUri(cr, input, data)
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe({ Toast.makeText(context, R.string.file_saved_successfully, Toast.LENGTH_SHORT).show() })
                    { Toast.makeText(context, R.string.generic_error, Toast.LENGTH_SHORT).show() })
        }
    
        override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
            var i = 0
            val n = permissions.size
            while (i < n) {
                val granted = grantResults[i] == PackageManager.PERMISSION_GRANTED
                when (permissions[i]) {
                    Manifest.permission.CAMERA -> {
                        presenter.cameraPermissionChanged(granted)
                        if (granted) {
                            if (requestCode == REQUEST_CODE_CAPTURE_VIDEO) {
                                sendVideoMessage()
                            } else if (requestCode == REQUEST_CODE_TAKE_PICTURE) {
                                takePicture()
                            }
                        }
                        return
                    }
                    Manifest.permission.RECORD_AUDIO -> {
                        if (granted) {
                            if (requestCode == REQUEST_CODE_CAPTURE_AUDIO) {
                                sendAudioMessage()
                            }
                        }
                        return
                    }
                    else -> {
                    }
                }
                i++
            }
        }
    
        override fun addElement(element: Interaction) {
            if (mLastRead != null && mLastRead == element.messageId) element.read()
            if (mAdapter!!.add(element)) scrollToEnd()
            loading = false
        }
    
        override fun updateElement(element: Interaction) {
            mAdapter!!.update(element)
        }
    
        override fun removeElement(element: Interaction) {
            mAdapter!!.remove(element)
        }
    
        override fun setComposingStatus(composingStatus: ComposingStatus) {
            mAdapter!!.setComposingStatus(composingStatus)
            if (composingStatus == ComposingStatus.Active) scrollToEnd()
        }
    
        override fun setLastDisplayed(interaction: Interaction) {
            mAdapter!!.setLastDisplayed(interaction)
        }
    
        override fun acceptFile(accountId: String, conversationUri: net.jami.model.Uri, transfer: DataTransfer) {
            if (transfer.messageId == null && transfer.fileId == null)
                return
            val cacheDir = requireContext().cacheDir
            val spaceLeft = getSpaceLeft(cacheDir.toString())
            if (spaceLeft == -1L || transfer.totalSize > spaceLeft) {
                presenter.noSpaceLeft()
                return
            }
            requireActivity().startService(Intent(DRingService.ACTION_FILE_ACCEPT, ConversationPath.toUri(accountId, conversationUri),
                requireContext(), DRingService::class.java)
                .putExtra(DRingService.KEY_MESSAGE_ID, transfer.messageId)
                .putExtra(DRingService.KEY_TRANSFER_ID, transfer.fileId)
            )
        }
    
        override fun refuseFile(accountId: String, conversationUri: net.jami.model.Uri, transfer: DataTransfer) {
            if (transfer.messageId == null && transfer.fileId == null)
                return
            requireActivity().startService(Intent(DRingService.ACTION_FILE_CANCEL, ConversationPath.toUri(accountId, conversationUri),
                requireContext(), DRingService::class.java)
                .putExtra(DRingService.KEY_MESSAGE_ID, transfer.messageId)
                .putExtra(DRingService.KEY_TRANSFER_ID, transfer.fileId)
            )
        }
    
        override fun shareFile(path: File, displayName: String) {
            val c = context ?: return
            var fileUri: Uri? = null
            try {
                fileUri = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path, displayName)
            } catch (e: IllegalArgumentException) {
                Log.e("File Selector", "The selected file can't be shared: " + path.name)
            }
            if (fileUri != null) {
                val sendIntent = Intent()
                sendIntent.action = Intent.ACTION_SEND
                sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                val type =
                    c.contentResolver.getType(fileUri.buildUpon().appendPath(displayName).build())
                sendIntent.setDataAndType(fileUri, type)
                sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri)
                startActivity(Intent.createChooser(sendIntent, null))
            }
        }
    
        override fun openFile(path: File, displayName: String) {
            val c = context ?: return
            var fileUri: Uri? = null
            try {
                fileUri = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path, displayName)
            } catch (e: IllegalArgumentException) {
                Log.e(TAG, "The selected file can't be shared: " + path.name)
            }
            if (fileUri != null) {
                val sendIntent = Intent()
                sendIntent.action = Intent.ACTION_VIEW
                sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                val type =
                    c.contentResolver.getType(fileUri.buildUpon().appendPath(displayName).build())
                sendIntent.setDataAndType(fileUri, type)
                sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri)
                //startActivity(Intent.createChooser(sendIntent, null));
                try {
                    startActivity(sendIntent)
                } catch (e: ActivityNotFoundException) {
                    Snackbar.make(requireView(), R.string.conversation_open_file_error, Snackbar.LENGTH_LONG)
                        .show()
                    Log.e("File Loader", "File of unknown type, could not open: " + path.name)
                }
            }
        }
    
        fun actionSendMsgText(actionId: Int): Boolean {
            when (actionId) {
                EditorInfo.IME_ACTION_SEND -> {
                    sendMessageText()
                    return true
                }
            }
            return false
        }
    
        fun onClick() {
            presenter.clickOnGoingPane()
        }
    
        override fun onStart() {
            super.onStart()
            presenter.resume(mIsBubble)
        }
    
        override fun onStop() {
            super.onStop()
            presenter.pause()
        }
    
        override fun onPause() {
            super.onPause()
            //presenter.pause();
        }
    
        override fun onResume() {
            super.onResume()
            //presenter.resume(mIsBubble);
        }
    
        override fun onDestroy() {
            mCompositeDisposable.dispose()
            super.onDestroy()
        }
    
        override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
            if (!isVisible) {
                return
            }
            inflater.inflate(R.menu.conversation_actions, menu)
            mAudioCallBtn = menu.findItem(R.id.conv_action_audiocall)
            mVideoCallBtn = menu.findItem(R.id.conv_action_videocall)
        }
    
        fun openContact() {
            presenter.openContact()
        }
    
        override fun onOptionsItemSelected(item: MenuItem): Boolean {
            val itemId = item.itemId
            if (itemId == android.R.id.home) {
                startActivity(Intent(activity, HomeActivity::class.java))
                return true
            } else if (itemId == R.id.conv_action_audiocall) {
                presenter.goToCall(false)
                return true
            } else if (itemId == R.id.conv_action_videocall) {
                presenter.goToCall(true)
                return true
            } else if (itemId == R.id.conv_contact_details) {
                presenter.openContact()
                return true
            }
            return super.onOptionsItemSelected(item)
        }
    
        override fun initPresenter(presenter: ConversationPresenter) {
            val path = ConversationPath.fromBundle(arguments)
            mIsBubble = requireArguments().getBoolean(NotificationServiceImpl.EXTRA_BUBBLE)
            Log.w(TAG, "initPresenter $path")
            if (path == null) return
            val uri = path.conversationUri
            mAdapter = ConversationAdapter(this, presenter)
            presenter.init(uri, path.accountId)
            try {
                mPreferences = getConversationPreferences(requireContext(), path.accountId, uri).also { preferences ->
                    preferences.registerOnSharedPreferenceChangeListener(this)
                    presenter.setConversationColor(preferences.getInt(KEY_PREFERENCE_CONVERSATION_COLOR, resources.getColor(R.color.color_primary_light)))
                    presenter.setConversationSymbol(preferences.getString(KEY_PREFERENCE_CONVERSATION_SYMBOL, resources.getText(R.string.conversation_default_emoji).toString())!!)
                    mLastRead = preferences.getString(KEY_PREFERENCE_CONVERSATION_LAST_READ, null)
                }
            } catch (e: Exception) {
                Log.e(TAG, "Can't load conversation preferences")
            }
            var connection = locationServiceConnection
            if (connection == null) {
                connection = object : ServiceConnection {
                    override fun onServiceConnected(name: ComponentName, service: IBinder) {
                        Log.w(TAG, "onServiceConnected")
                        val binder = service as LocationSharingService.LocalBinder
                        val locationService = binder.service
                        //val path = ConversationPath(presenter.path)
                        if (locationService.isSharing(path)) {
                            showMap(path.accountId, uri.uri, false)
                        }
                        /*try {
                            requireContext().unbindService(locationServiceConnection!!)
                        } catch (e: Exception) {
                            Log.w(TAG, "Error unbinding service", e)
                        }*/
                    }
    
                    override fun onServiceDisconnected(name: ComponentName) {
                        Log.w(TAG, "onServiceDisconnected")
                        locationServiceConnection = null
                    }
                }
                locationServiceConnection = connection
                Log.w(TAG, "bindService")
                requireContext().bindService(Intent(requireContext(), LocationSharingService::class.java), connection, 0)
            }
        }
    
        override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
            when (key) {
                KEY_PREFERENCE_CONVERSATION_COLOR -> presenter.setConversationColor(
                    prefs.getInt(KEY_PREFERENCE_CONVERSATION_COLOR, resources.getColor(R.color.color_primary_light)))
                KEY_PREFERENCE_CONVERSATION_SYMBOL -> presenter.setConversationSymbol(
                    prefs.getString(KEY_PREFERENCE_CONVERSATION_SYMBOL, resources.getText(R.string.conversation_default_emoji).toString())!!)
            }
        }
    
        override fun updateContact(contact: Contact) {
            val contactKey = contact.primaryNumber
            val a = mSmallParticipantAvatars[contactKey]
            if (a != null) {
                a.update(contact)
                mParticipantAvatars[contactKey]!!.update(contact)
                mAdapter?.setPhoto()
            } else {
                mCompositeDisposable.add(AvatarFactory.getAvatar(requireContext(), contact, true)
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe { avatar ->
                        mParticipantAvatars[contactKey] = avatar as AvatarDrawable
                        mSmallParticipantAvatars[contactKey] = AvatarDrawable.Builder()
                            .withContact(contact)
                            .withCircleCrop(true)
                            .withPresence(false)
                            .build(requireContext())
                        mAdapter?.setPhoto()
                    })
            }
        }
    
        override fun displayContact(conversation: Conversation) {
            mCompositeDisposable.add(AvatarFactory.getAvatar(requireContext(), conversation, true)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe { d ->
                    mConversationAvatar = d as AvatarDrawable
                    mParticipantAvatars[conversation.uri.rawRingId] = AvatarDrawable(d)
                    setupActionbar(conversation)
                })
        }
    
        override fun displayOnGoingCallPane(display: Boolean) {
            binding!!.ongoingcallPane.visibility = if (display) View.VISIBLE else View.GONE
        }
    
        override fun displayNumberSpinner(conversation: Conversation, number: net.jami.model.Uri) {
            binding!!.numberSelector.visibility = View.VISIBLE
            //binding.numberSelector.setAdapter(new NumberAdapter(getActivity(), conversation.getContact(), false));
            binding!!.numberSelector.setSelection(getIndex(binding!!.numberSelector, number))
        }
    
        override fun hideNumberSpinner() {
            binding!!.numberSelector.visibility = View.GONE
        }
    
        override fun clearMsgEdit() {
            binding!!.msgInputTxt.setText("")
        }
    
        override fun goToHome() {
            if (activity is ConversationActivity) {
                requireActivity().finish()
            }
        }
    
        override fun goToAddContact(contact: Contact) {
            startActivityForResult(ActionHelper.getAddNumberIntentForContact(contact), REQ_ADD_CONTACT)
        }
    
    
        override fun goToContactActivity(accountId: String, uri: net.jami.model.Uri) {
            val toolbar: Toolbar = requireActivity().findViewById(R.id.main_toolbar)
            val logo = toolbar.findViewById<ImageView>(R.id.contact_image)
            startActivity(Intent(Intent.ACTION_VIEW, ConversationPath.toUri(accountId, uri))
                    .setClass(requireContext().applicationContext, ContactDetailsActivity::class.java),
                ActivityOptions.makeSceneTransitionAnimation(activity, logo, "conversationIcon")
                    .toBundle())
        }
    
        override fun goToCallActivity(conferenceId: String, withCamera: Boolean) {
            Log.w(TAG, "DEBUG fn goToCallActivity")
            startActivity(Intent(Intent.ACTION_VIEW)
                .setClass(requireContext().applicationContext, CallActivity::class.java)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                .putExtra(NotificationService.KEY_CALL_ID, conferenceId)
            )
        }
    
        override fun goToCallActivityWithResult(accountId: String, conversationUri: net.jami.model.Uri, contactUri: net.jami.model.Uri, withCamera: Boolean) {
            Log.w(TAG, "DEBUG fn goToCallActivityWithResult || hasVideo : $withCamera")
            val intent = Intent(Intent.ACTION_CALL)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                .setClass(requireContext(), CallActivity::class.java)
                .putExtras(ConversationPath.toBundle(accountId, conversationUri))
                .putExtra(Intent.EXTRA_PHONE_NUMBER, contactUri.uri)
                .putExtra(CallFragment.KEY_HAS_VIDEO, withCamera)
            startActivity(intent)
        }
    
        private fun setupActionbar(conversation: Conversation) {
            if (!isVisible) {
                return
            }
            val activity: Activity = requireActivity()
            val displayName = conversation.title
            val identity = conversation.uriTitle
            val toolbar: Toolbar = activity.findViewById(R.id.main_toolbar)
            val title = toolbar.findViewById<TextView>(R.id.contact_title)
            val subtitle = toolbar.findViewById<TextView>(R.id.contact_subtitle)
            val logo = toolbar.findViewById<ImageView>(R.id.contact_image)
            logo.setImageDrawable(mConversationAvatar)
            logo.visibility = View.VISIBLE
            title.text = displayName
            title.textSize = 15f
            title.setTypeface(null, Typeface.NORMAL)
            if (identity != null && identity != displayName) {
                subtitle.text = identity
                subtitle.visibility = View.VISIBLE
                /*RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) title.getLayoutParams();
                params.addRule(RelativeLayout.ALIGN_TOP, R.id.contact_image);
                title.setLayoutParams(params);*/
            } else {
                subtitle.text = ""
                subtitle.visibility = View.GONE
    
                /*RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) title.getLayoutParams();
                params.removeRule(RelativeLayout.ALIGN_TOP);
                params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
                title.setLayoutParams(params);*/
            }
        }
    
        fun blockContactRequest() {
            presenter.onBlockIncomingContactRequest()
        }
    
        fun refuseContactRequest() {
            presenter.onRefuseIncomingContactRequest()
        }
    
        fun acceptContactRequest() {
            presenter.onAcceptIncomingContactRequest()
        }
    
        fun addContact() {
            presenter.onAddContact()
        }
    
        override fun onPrepareOptionsMenu(menu: Menu) {
            super.onPrepareOptionsMenu(menu)
            val visible = binding!!.cvMessageInput.visibility == View.VISIBLE
            if (mAudioCallBtn != null) mAudioCallBtn!!.isVisible = visible
            if (mVideoCallBtn != null) mVideoCallBtn!!.isVisible = visible
        }
    
        override fun switchToUnknownView(contactDisplayName: String) {
            binding?.apply {
                cvMessageInput.visibility = View.GONE
                unknownContactPrompt.visibility = View.VISIBLE
                trustRequestPrompt.visibility = View.GONE
                tvTrustRequestMessage.text = String.format(getString(R.string.message_contact_not_trusted), contactDisplayName)
                trustRequestMessageLayout.visibility = View.VISIBLE
                currentBottomView = unknownContactPrompt
            }
            requireActivity().invalidateOptionsMenu()
            updateListPadding()
        }
    
        override fun switchToIncomingTrustRequestView(contactDisplayName: String) {
            binding?.apply {
                cvMessageInput.visibility = View.GONE
                unknownContactPrompt.visibility = View.GONE
                trustRequestPrompt.visibility = View.VISIBLE
                tvTrustRequestMessage.text = String.format(getString(R.string.message_contact_not_trusted_yet), contactDisplayName)
                trustRequestMessageLayout.visibility = View.VISIBLE
                currentBottomView = trustRequestPrompt
            }
            requireActivity().invalidateOptionsMenu()
            updateListPadding()
        }
    
        override fun switchToConversationView() {
            binding?.apply {
                cvMessageInput.visibility = View.VISIBLE
                unknownContactPrompt.visibility = View.GONE
                trustRequestPrompt.visibility = View.GONE
                trustRequestMessageLayout.visibility = View.GONE
                currentBottomView = cvMessageInput
            }
            requireActivity().invalidateOptionsMenu()
            updateListPadding()
        }
    
        override fun switchToSyncingView() {
            binding?.apply {
                cvMessageInput.visibility = View.GONE
                unknownContactPrompt.visibility = View.GONE
                trustRequestPrompt.visibility = View.GONE
                trustRequestMessageLayout.visibility = View.VISIBLE
                tvTrustRequestMessage.text = "Syncing conversation..."
            }
            currentBottomView = null
            requireActivity().invalidateOptionsMenu()
            updateListPadding()
        }
        override fun switchToEndedView() {
            binding?.apply {
                cvMessageInput.visibility = View.GONE
                unknownContactPrompt.visibility = View.GONE
                trustRequestPrompt.visibility = View.GONE
                trustRequestMessageLayout.visibility = View.VISIBLE
                tvTrustRequestMessage.text = "Conversation ended"
            }
            currentBottomView = null
            requireActivity().invalidateOptionsMenu()
            updateListPadding()
        }
    
        override fun positiveMediaButtonClicked() {
            presenter.clickOnGoingPane()
        }
    
        override fun negativeMediaButtonClicked() {
            presenter.clickOnGoingPane()
        }
    
        override fun toggleMediaButtonClicked() {
            presenter.clickOnGoingPane()
        }
    
        private fun setLoading(isLoading: Boolean) {
            if (binding == null) return
            if (isLoading) {
                binding!!.btnTakePicture.visibility = View.GONE
                binding!!.pbDataTransfer.visibility = View.VISIBLE
            } else {
                binding!!.btnTakePicture.visibility = View.VISIBLE
                binding!!.pbDataTransfer.visibility = View.GONE
            }
        }
    
        fun handleShareIntent(intent: Intent) {
            Log.w(TAG, "handleShareIntent $intent")
            val action = intent.action
            if (Intent.ACTION_SEND == action || Intent.ACTION_SEND_MULTIPLE == action) {
                val type = intent.type
                if (type == null) {
                    Log.w(TAG, "Can't share with no type")
                    return
                }
                if (type.startsWith("text/plain")) {
                    binding!!.msgInputTxt.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
                } else {
                    var uri = intent.data
                    val clip = intent.clipData
                    if (uri == null && clip != null && clip.itemCount > 0) uri = clip.getItemAt(0).uri
                    if (uri == null) return
                    startFileSend(getCacheFile(requireContext(), uri).flatMapCompletable { file -> sendFile(file) })
                }
            } else if (Intent.ACTION_VIEW == action) {
                val path = ConversationPath.fromIntent(intent)
                if (path != null && intent.getBooleanExtra(EXTRA_SHOW_MAP, false)) {
                    shareLocation()
                }
            }
        }
    
        /**
         * Creates an intent using Android Storage Access Framework
         * This intent is then received by applications that can handle it like
         * Downloads or Google drive
         * @param file DataTransfer of the file that is going to be stored
         * @param currentFileAbsolutePath absolute path of the file we want to save
         */
        override fun startSaveFile(file: DataTransfer, currentFileAbsolutePath: String) {
            //Get the current file absolute path and store it
            mCurrentFileAbsolutePath = currentFileAbsolutePath
            try {
                //Use Android Storage File Access to download the file
                val downloadFileIntent = Intent(Intent.ACTION_CREATE_DOCUMENT)
                downloadFileIntent.type = getMimeTypeFromExtension(file.extension)
                downloadFileIntent.addCategory(Intent.CATEGORY_OPENABLE)
                downloadFileIntent.putExtra(Intent.EXTRA_TITLE, file.displayName)
                startActivityForResult(downloadFileIntent, REQUEST_CODE_SAVE_FILE)
            } catch (e: Exception) {
                Log.i(TAG, "No app detected for saving files.")
                val directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                if (!directory.exists()) {
                    directory.mkdirs()
                }
                writeToFile(Uri.fromFile(File(directory, file.displayName)))
            }
        }
    
        override fun displayNetworkErrorPanel() {
            binding?.apply {
                errorMsgPane.visibility = View.VISIBLE
                errorMsgPane.setOnClickListener(null)
                errorMsgPane.setText(R.string.error_no_network)
            }
        }
    
        override fun displayAccountOfflineErrorPanel() {
            binding?.apply {
                errorMsgPane.visibility = View.VISIBLE
                errorMsgPane.setOnClickListener(null)
                errorMsgPane.setText(R.string.error_account_offline)
                for (idx in 0 until btnContainer.childCount) {
                    btnContainer.getChildAt(idx).isEnabled = false
                }
            }
        }
    
        override fun setSettings(readIndicator: Boolean, linkPreviews: Boolean) {
            mAdapter?.apply {
                setReadIndicatorStatus(readIndicator)
                showLinkPreviews = linkPreviews
            }
        }
    
        override fun updateLastRead(last: String) {
            Log.w(TAG, "Updated last read $mLastRead")
            mLastRead = last
            mPreferences?.edit()?.putString(KEY_PREFERENCE_CONVERSATION_LAST_READ, last)?.apply()
        }
    
        override fun hideErrorPanel() {
            binding?.errorMsgPane?.visibility = View.GONE
        }
    
        companion object {
            private val TAG = ConversationFragment::class.java.simpleName
            const val REQ_ADD_CONTACT = 42
            const val KEY_PREFERENCE_PENDING_MESSAGE = "pendingMessage"
            const val KEY_PREFERENCE_CONVERSATION_COLOR = "color"
            const val KEY_PREFERENCE_CONVERSATION_LAST_READ = "lastRead"
            const val KEY_PREFERENCE_CONVERSATION_SYMBOL = "symbol"
            const val EXTRA_SHOW_MAP = "showMap"
            private const val REQUEST_CODE_FILE_PICKER = 1000
            private const val REQUEST_PERMISSION_CAMERA = 1001
            private const val REQUEST_CODE_TAKE_PICTURE = 1002
            private const val REQUEST_CODE_SAVE_FILE = 1003
            private const val REQUEST_CODE_CAPTURE_AUDIO = 1004
            private const val REQUEST_CODE_CAPTURE_VIDEO = 1005
            private fun getIndex(spinner: Spinner, myString: net.jami.model.Uri): Int {
                var i = 0
                val n = spinner.count
                while (i < n) {
                    if ((spinner.getItemAtPosition(i) as Phone).number == myString) {
                        return i
                    }
                    i++
                }
                return 0
            }
    
            private fun setBottomPadding(view: View, padding: Int) {
                view.setPadding(view.paddingLeft, view.paddingTop, view.paddingRight, padding)
            }
        }
    }