RingtoneSelectionController.kt 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. /*
  2. * Nextcloud Talk application
  3. *
  4. * @author Mario Danic
  5. * @author Andy Scherzinger
  6. * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
  7. * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation, either version 3 of the License, or
  12. * at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. */
  22. package com.nextcloud.talk.controllers
  23. import android.annotation.SuppressLint
  24. import android.media.MediaPlayer
  25. import android.media.RingtoneManager
  26. import android.net.Uri
  27. import android.os.Bundle
  28. import android.os.Handler
  29. import android.text.TextUtils
  30. import android.util.Log
  31. import android.view.MenuItem
  32. import android.view.View
  33. import androidx.recyclerview.widget.RecyclerView
  34. import autodagger.AutoInjector
  35. import com.bluelinelabs.logansquare.LoganSquare
  36. import com.nextcloud.talk.R
  37. import com.nextcloud.talk.adapters.items.NotificationSoundItem
  38. import com.nextcloud.talk.application.NextcloudTalkApplication
  39. import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
  40. import com.nextcloud.talk.controllers.base.BaseController
  41. import com.nextcloud.talk.controllers.util.viewBinding
  42. import com.nextcloud.talk.databinding.ControllerGenericRvBinding
  43. import com.nextcloud.talk.models.RingtoneSettings
  44. import com.nextcloud.talk.utils.NotificationUtils
  45. import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ARE_CALL_SOUNDS
  46. import eu.davidea.flexibleadapter.FlexibleAdapter
  47. import eu.davidea.flexibleadapter.SelectableAdapter
  48. import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
  49. import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
  50. import java.io.IOException
  51. @AutoInjector(NextcloudTalkApplication::class)
  52. class RingtoneSelectionController(args: Bundle) :
  53. BaseController(
  54. R.layout.controller_generic_rv,
  55. args
  56. ),
  57. FlexibleAdapter.OnItemClickListener {
  58. private val binding: ControllerGenericRvBinding? by viewBinding(ControllerGenericRvBinding::bind)
  59. private var adapter: FlexibleAdapter<*>? = null
  60. private var adapterDataObserver: RecyclerView.AdapterDataObserver? = null
  61. private val abstractFlexibleItemList: MutableList<AbstractFlexibleItem<*>> = ArrayList()
  62. private val callNotificationSounds: Boolean
  63. private var mediaPlayer: MediaPlayer? = null
  64. private var cancelMediaPlayerHandler: Handler? = null
  65. override fun onViewBound(view: View) {
  66. super.onViewBound(view)
  67. if (adapter == null) {
  68. adapter = FlexibleAdapter(abstractFlexibleItemList, activity, false)
  69. adapter!!.setNotifyChangeOfUnfilteredItems(true).mode = SelectableAdapter.Mode.SINGLE
  70. adapter!!.addListener(this)
  71. cancelMediaPlayerHandler = Handler()
  72. }
  73. adapter!!.addListener(this)
  74. prepareViews()
  75. fetchNotificationSounds()
  76. }
  77. override fun onOptionsItemSelected(item: MenuItem): Boolean {
  78. return if (item.itemId == android.R.id.home) {
  79. router.popCurrentController()
  80. } else {
  81. super.onOptionsItemSelected(item)
  82. }
  83. }
  84. private fun prepareViews() {
  85. val layoutManager: RecyclerView.LayoutManager = SmoothScrollLinearLayoutManager(activity)
  86. binding?.recyclerView?.layoutManager = layoutManager
  87. binding?.recyclerView?.setHasFixedSize(true)
  88. binding?.recyclerView?.adapter = adapter
  89. adapterDataObserver = object : RecyclerView.AdapterDataObserver() {
  90. override fun onChanged() {
  91. super.onChanged()
  92. findSelectedSound()
  93. }
  94. }
  95. adapter!!.registerAdapterDataObserver(adapterDataObserver!!)
  96. binding?.swipeRefreshLayout?.isEnabled = false
  97. }
  98. @SuppressLint("LongLogTag")
  99. private fun findSelectedSound() {
  100. var foundDefault = false
  101. var preferencesString: String? = null
  102. val callsEnabledButNoRingtone = callNotificationSounds &&
  103. TextUtils.isEmpty(appPreferences.callRingtoneUri.also { preferencesString = it })
  104. val noCallsAndNoMessageTone = !callNotificationSounds &&
  105. TextUtils.isEmpty(appPreferences.messageRingtoneUri.also { preferencesString = it })
  106. if (callsEnabledButNoRingtone || noCallsAndNoMessageTone) {
  107. adapter!!.toggleSelection(1)
  108. foundDefault = true
  109. }
  110. if (!TextUtils.isEmpty(preferencesString) && !foundDefault) {
  111. try {
  112. val ringtoneSettings: RingtoneSettings =
  113. LoganSquare.parse<RingtoneSettings>(preferencesString, RingtoneSettings::class.java)
  114. if (ringtoneSettings.ringtoneUri == null) {
  115. adapter!!.toggleSelection(0)
  116. } else if (ringtoneSettings.ringtoneUri!!.toString() == ringtoneString) {
  117. adapter!!.toggleSelection(1)
  118. } else {
  119. var notificationSoundItem: NotificationSoundItem?
  120. for (i in 2 until adapter!!.itemCount) {
  121. notificationSoundItem = adapter!!.getItem(i) as NotificationSoundItem?
  122. if (
  123. notificationSoundItem!!.notificationSoundUri == ringtoneSettings.ringtoneUri!!.toString()
  124. ) {
  125. adapter!!.toggleSelection(i)
  126. break
  127. }
  128. }
  129. }
  130. } catch (e: IOException) {
  131. Log.e(TAG, "Failed to parse ringtone settings")
  132. }
  133. }
  134. adapter!!.unregisterAdapterDataObserver(adapterDataObserver!!)
  135. adapterDataObserver = null
  136. }
  137. private val ringtoneString: String
  138. get() = if (callNotificationSounds) {
  139. NotificationUtils.DEFAULT_CALL_RINGTONE_URI
  140. } else {
  141. NotificationUtils.DEFAULT_MESSAGE_RINGTONE_URI
  142. }
  143. private fun fetchNotificationSounds() {
  144. abstractFlexibleItemList.add(
  145. NotificationSoundItem(
  146. resources!!.getString(R.string.nc_settings_no_ringtone),
  147. null
  148. )
  149. )
  150. abstractFlexibleItemList.add(
  151. NotificationSoundItem(
  152. resources!!.getString(R.string.nc_settings_default_ringtone),
  153. ringtoneString
  154. )
  155. )
  156. if (activity != null) {
  157. val manager = RingtoneManager(activity)
  158. if (callNotificationSounds) {
  159. manager.setType(RingtoneManager.TYPE_RINGTONE)
  160. } else {
  161. manager.setType(RingtoneManager.TYPE_NOTIFICATION)
  162. }
  163. val cursor = manager.cursor
  164. var notificationSoundItem: NotificationSoundItem
  165. while (cursor.moveToNext()) {
  166. val notificationTitle = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX)
  167. val notificationUri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX)
  168. val completeNotificationUri = notificationUri + "/" + cursor.getString(RingtoneManager.ID_COLUMN_INDEX)
  169. notificationSoundItem = NotificationSoundItem(notificationTitle, completeNotificationUri)
  170. abstractFlexibleItemList.add(notificationSoundItem)
  171. }
  172. }
  173. adapter!!.updateDataSet(abstractFlexibleItemList as List<Nothing>?, false)
  174. }
  175. override fun onItemClick(view: View, position: Int): Boolean {
  176. val notificationSoundItem = adapter!!.getItem(position) as NotificationSoundItem?
  177. var ringtoneUri: Uri? = null
  178. if (!TextUtils.isEmpty(notificationSoundItem!!.notificationSoundUri)) {
  179. ringtoneUri = Uri.parse(notificationSoundItem.notificationSoundUri)
  180. endMediaPlayer()
  181. mediaPlayer = MediaPlayer.create(activity, ringtoneUri)
  182. cancelMediaPlayerHandler = Handler()
  183. cancelMediaPlayerHandler!!.postDelayed(
  184. { endMediaPlayer() },
  185. (mediaPlayer!!.duration + DURATION_EXTENSION).toLong()
  186. )
  187. mediaPlayer!!.start()
  188. }
  189. if (adapter!!.selectedPositions.size == 0 || adapter!!.selectedPositions[0] != position) {
  190. val ringtoneSettings = RingtoneSettings()
  191. ringtoneSettings.ringtoneName = notificationSoundItem.notificationSoundName
  192. ringtoneSettings.ringtoneUri = ringtoneUri
  193. if (callNotificationSounds) {
  194. try {
  195. appPreferences!!.callRingtoneUri = LoganSquare.serialize(ringtoneSettings)
  196. adapter!!.toggleSelection(position)
  197. adapter!!.notifyDataSetChanged()
  198. } catch (e: IOException) {
  199. Log.e(TAG, "Failed to store selected ringtone for calls")
  200. }
  201. } else {
  202. try {
  203. appPreferences!!.messageRingtoneUri = LoganSquare.serialize(ringtoneSettings)
  204. adapter!!.toggleSelection(position)
  205. adapter!!.notifyDataSetChanged()
  206. } catch (e: IOException) {
  207. Log.e(TAG, "Failed to store selected ringtone for calls")
  208. }
  209. }
  210. }
  211. return true
  212. }
  213. private fun endMediaPlayer() {
  214. if (cancelMediaPlayerHandler != null) {
  215. cancelMediaPlayerHandler!!.removeCallbacksAndMessages(null)
  216. }
  217. if (mediaPlayer != null) {
  218. if (mediaPlayer!!.isPlaying) {
  219. mediaPlayer!!.stop()
  220. }
  221. mediaPlayer!!.release()
  222. mediaPlayer = null
  223. }
  224. }
  225. public override fun onDestroy() {
  226. endMediaPlayer()
  227. super.onDestroy()
  228. }
  229. companion object {
  230. private const val TAG = "RingtoneSelection"
  231. private const val DURATION_EXTENSION = 25
  232. }
  233. init {
  234. setHasOptionsMenu(true)
  235. sharedApplication!!.componentApplication.inject(this)
  236. callNotificationSounds = args.getBoolean(KEY_ARE_CALL_SOUNDS, false)
  237. }
  238. override val title: String
  239. get() =
  240. resources!!.getString(R.string.nc_settings_notification_sounds)
  241. }