diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a4d8f48..c37a223f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #1603 - Remove deprecated success path for Firefox Accounts login - #619 - Sets toolbar behavior based on accessibility and if session is loading - #1571 - Added a snackbar for undoing bookmark deletion +- #1079 - Managing site permissions exceptions ### Changed - #1429 - Updated site permissions ui for MVP diff --git a/app/src/main/java/org/mozilla/fenix/components/Storage.kt b/app/src/main/java/org/mozilla/fenix/components/Storage.kt index 38e17673f..7c257687a 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Storage.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Storage.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.components import android.content.Context +import androidx.paging.DataSource import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.feature.sitepermissions.SitePermissions.Status import mozilla.components.feature.sitepermissions.SitePermissionsStorage @@ -41,4 +42,16 @@ class Storage(private val context: Context) { fun updateSitePermissions(sitePermissions: SitePermissions) { permissionsStorage.update(sitePermissions) } + + fun getSitePermissionsPaged(): DataSource.Factory { + return permissionsStorage.getSitePermissionsPaged() + } + + fun deleteSitePermissions(sitePermissions: SitePermissions) { + permissionsStorage.remove(sitePermissions) + } + + fun deleteAllSitePermissions() { + permissionsStorage.removeAll() + } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/Extensions.kt b/app/src/main/java/org/mozilla/fenix/settings/Extensions.kt index fe7caffc0..60d61beb6 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/Extensions.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/Extensions.kt @@ -5,6 +5,9 @@ package org.mozilla.fenix.settings import android.content.Context +import android.view.View +import android.widget.TextView +import androidx.core.text.HtmlCompat import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.feature.sitepermissions.SitePermissionsRules import org.mozilla.fenix.R @@ -73,3 +76,37 @@ fun SitePermissions.toggle(featurePhone: PhoneFeature): SitePermissions { } } } + +fun PhoneFeature.getLabel(context: Context): String { + return when (this) { + PhoneFeature.CAMERA -> context.getString(R.string.preference_phone_feature_camera) + PhoneFeature.LOCATION -> context.getString(R.string.preference_phone_feature_location) + PhoneFeature.MICROPHONE -> context.getString(R.string.preference_phone_feature_microphone) + PhoneFeature.NOTIFICATION -> context.getString(R.string.preference_phone_feature_notification) + } +} + +fun PhoneFeature.getPreferenceKey(context: Context): String { + return when (this) { + PhoneFeature.CAMERA -> context.getString(R.string.pref_key_phone_feature_camera) + PhoneFeature.LOCATION -> context.getString(R.string.pref_key_phone_feature_location) + PhoneFeature.MICROPHONE -> context.getString(R.string.pref_key_phone_feature_microphone) + PhoneFeature.NOTIFICATION -> context.getString(R.string.pref_key_phone_feature_notification) + } +} + +fun initBlockedByAndroidView(phoneFeature: PhoneFeature, blockedByAndroidView: View) { + val context = blockedByAndroidView.context + if (!phoneFeature.isAndroidPermissionGranted(context)) { + blockedByAndroidView.visibility = View.VISIBLE + + val descriptionLabel = blockedByAndroidView.findViewById(R.id.blocked_by_android_explanation_label) + val text = context.getString( + R.string.phone_feature_blocked_by_android_explanation, + phoneFeature.getLabel(context) + ) + descriptionLabel.text = HtmlCompat.fromHtml(text, HtmlCompat.FROM_HTML_MODE_COMPACT) + } else { + blockedByAndroidView.visibility = View.GONE + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt b/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt index 544604b77..864db2580 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt @@ -4,7 +4,6 @@ package org.mozilla.fenix.settings -import android.Manifest import android.Manifest.permission.ACCESS_COARSE_LOCATION import android.Manifest.permission.ACCESS_FINE_LOCATION import android.Manifest.permission.RECORD_AUDIO @@ -13,16 +12,17 @@ import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.support.ktx.android.content.isPermissionGranted import org.mozilla.fenix.utils.Settings +const val ID_CAMERA_PERMISSION = 0 +const val ID_LOCATION_PERMISSION = 1 +const val ID_MICROPHONE_PERMISSION = 2 +const val ID_NOTIFICATION_PERMISSION = 3 +private const val CAMERA_PERMISSION = android.Manifest.permission.CAMERA + enum class PhoneFeature(val id: Int, val androidPermissionsList: Array) { - CAMERA(SitePermissionsManagePhoneFeature.CAMERA_PERMISSION, arrayOf(Manifest.permission.CAMERA)), - LOCATION( - SitePermissionsManagePhoneFeature.LOCATION_PERMISSION, arrayOf( - ACCESS_COARSE_LOCATION, - ACCESS_FINE_LOCATION - ) - ), - MICROPHONE(SitePermissionsManagePhoneFeature.MICROPHONE_PERMISSION, arrayOf(RECORD_AUDIO)), - NOTIFICATION(SitePermissionsManagePhoneFeature.NOTIFICATION_PERMISSION, emptyArray()); + CAMERA(ID_CAMERA_PERMISSION, arrayOf(CAMERA_PERMISSION)), + LOCATION(ID_LOCATION_PERMISSION, arrayOf(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION)), + MICROPHONE(ID_MICROPHONE_PERMISSION, arrayOf(RECORD_AUDIO)), + NOTIFICATION(ID_NOTIFICATION_PERMISSION, emptyArray()); @Suppress("SpreadOperator") fun isAndroidPermissionGranted(context: Context): Boolean { @@ -33,54 +33,56 @@ enum class PhoneFeature(val id: Int, val androidPermissionsList: Array) return context.isPermissionGranted(*permissions) } - fun getActionLabel(context: Context, sitePermissions: SitePermissions? = null, settings: Settings): String { - return when (this) { + fun getActionLabel(context: Context, sitePermissions: SitePermissions? = null, settings: Settings? = null): String { + val label = when (this) { CAMERA -> { sitePermissions?.camera?.toString(context) ?: settings - .getSitePermissionsPhoneFeatureCameraAction() - .toString(context) + ?.getSitePermissionsPhoneFeatureCameraAction() + ?.toString(context) } LOCATION -> { sitePermissions?.location?.toString(context) ?: settings - .getSitePermissionsPhoneFeatureLocation() - .toString(context) + ?.getSitePermissionsPhoneFeatureLocation() + ?.toString(context) } MICROPHONE -> { sitePermissions?.microphone?.toString(context) ?: settings - .getSitePermissionsPhoneFeatureMicrophoneAction() - .toString(context) + ?.getSitePermissionsPhoneFeatureMicrophoneAction() + ?.toString(context) } NOTIFICATION -> { sitePermissions?.notification?.toString(context) ?: settings - .getSitePermissionsPhoneFeatureNotificationAction() - .toString(context) + ?.getSitePermissionsPhoneFeatureNotificationAction() + ?.toString(context) } } + return requireNotNull(label) } - fun getStatus(sitePermissions: SitePermissions? = null, settings: Settings): SitePermissions.Status { - return when (this) { + fun getStatus(sitePermissions: SitePermissions? = null, settings: Settings? = null): SitePermissions.Status { + val status = when (this) { CAMERA -> { sitePermissions?.camera ?: settings - .getSitePermissionsPhoneFeatureCameraAction() - .toStatus() + ?.getSitePermissionsPhoneFeatureCameraAction() + ?.toStatus() } LOCATION -> { sitePermissions?.location ?: settings - .getSitePermissionsPhoneFeatureLocation() - .toStatus() + ?.getSitePermissionsPhoneFeatureLocation() + ?.toStatus() } MICROPHONE -> { sitePermissions?.microphone ?: settings - .getSitePermissionsPhoneFeatureMicrophoneAction() - .toStatus() + ?.getSitePermissionsPhoneFeatureMicrophoneAction() + ?.toStatus() } NOTIFICATION -> { sitePermissions?.notification ?: settings - .getSitePermissionsPhoneFeatureNotificationAction() - .toStatus() + ?.getSitePermissionsPhoneFeatureNotificationAction() + ?.toStatus() } } + return requireNotNull(status) } companion object { diff --git a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsDetailsExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsDetailsExceptionsFragment.kt new file mode 100644 index 000000000..ea4a82d07 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsDetailsExceptionsFragment.kt @@ -0,0 +1,130 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.settings + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.Navigation +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import mozilla.components.feature.sitepermissions.SitePermissions +import org.jetbrains.anko.alert +import org.jetbrains.anko.noButton +import org.jetbrains.anko.yesButton +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.settings.PhoneFeature.CAMERA +import org.mozilla.fenix.settings.PhoneFeature.MICROPHONE +import org.mozilla.fenix.settings.PhoneFeature.LOCATION +import org.mozilla.fenix.settings.PhoneFeature.NOTIFICATION +import kotlin.coroutines.CoroutineContext + +@SuppressWarnings("TooManyFunctions") +class SitePermissionsDetailsExceptionsFragment : PreferenceFragmentCompat(), CoroutineScope { + private lateinit var job: Job + override val coroutineContext: CoroutineContext get() = Dispatchers.IO + job + private lateinit var sitePermissions: SitePermissions + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + (activity as AppCompatActivity).supportActionBar?.show() + + sitePermissions = SitePermissionsDetailsExceptionsFragmentArgs + .fromBundle(requireArguments()) + .sitePermissions + + (activity as AppCompatActivity).title = sitePermissions.origin + job = Job() + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.site_permissions_details_exceptions_preferences, rootKey) + } + + override fun onResume() { + super.onResume() + launch(IO) { + val context = requireContext() + sitePermissions = requireNotNull(context.components.storage.findSitePermissionsBy(sitePermissions.origin)) + launch(Main) { + bindCategoryPhoneFeatures() + } + } + } + + override fun onDestroy() { + super.onDestroy() + job.cancel() + } + + private fun bindCategoryPhoneFeatures() { + val context = requireContext() + + val cameraAction = CAMERA.getActionLabel(context, sitePermissions) + val locationAction = LOCATION.getActionLabel(context, sitePermissions) + val microphoneAction = MICROPHONE.getActionLabel(context, sitePermissions) + val notificationAction = NOTIFICATION.getActionLabel(context, sitePermissions) + + initPhoneFeature(CAMERA, cameraAction) + initPhoneFeature(LOCATION, locationAction) + initPhoneFeature(MICROPHONE, microphoneAction) + initPhoneFeature(NOTIFICATION, notificationAction) + bindClearPermissionsButton() + } + + private fun initPhoneFeature(phoneFeature: PhoneFeature, summary: String) { + val keyPreference = phoneFeature.getPreferenceKey(requireContext()) + val cameraPhoneFeatures: Preference = requireNotNull(findPreference(keyPreference)) + cameraPhoneFeatures.summary = summary + + cameraPhoneFeatures.onPreferenceClickListener = Preference.OnPreferenceClickListener { + navigateToPhoneFeature(phoneFeature) + true + } + } + + private fun bindClearPermissionsButton() { + val keyPreference = getString(R.string.pref_key_exceptions_clear_site_permissions) + val button: Preference = requireNotNull(findPreference(keyPreference)) + + button.onPreferenceClickListener = Preference.OnPreferenceClickListener { + requireContext().alert( + R.string.confirm_clear_permissions_site, + R.string.clear_permissions + ) { + yesButton { + clearSitePermissions() + } + noButton { } + }.show() + + true + } + } + + private fun clearSitePermissions() { + launch(IO) { + requireContext().components.storage.deleteSitePermissions(sitePermissions) + launch(Main) { + Navigation.findNavController(requireNotNull(view)).popBackStack() + } + } + } + + private fun navigateToPhoneFeature(phoneFeature: PhoneFeature) { + val directions = + SitePermissionsDetailsExceptionsFragmentDirections.actionSitePermissionsToExceptionsToManagePhoneFeature( + phoneFeatureId = phoneFeature.id, + sitePermissions = sitePermissions + ) + Navigation.findNavController(view!!).navigate(directions) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsExceptionsFragment.kt new file mode 100644 index 000000000..50be9fd0d --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsExceptionsFragment.kt @@ -0,0 +1,194 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.settings + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.navigation.Navigation +import androidx.paging.LivePagedListBuilder +import androidx.paging.PagedList +import androidx.paging.PagedListAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import mozilla.components.browser.icons.BrowserIcons +import mozilla.components.browser.icons.IconRequest +import mozilla.components.feature.sitepermissions.SitePermissions +import org.jetbrains.anko.alert +import org.jetbrains.anko.noButton +import org.jetbrains.anko.yesButton +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.components +import kotlin.coroutines.CoroutineContext +import android.graphics.drawable.BitmapDrawable + +private const val MAX_ITEMS_PER_PAGE = 50 + +@SuppressWarnings("TooManyFunctions") +class SitePermissionsExceptionsFragment : Fragment(), View.OnClickListener, CoroutineScope { + private lateinit var emptyContainerMessage: View + private lateinit var recyclerView: RecyclerView + private lateinit var clearButton: Button + private lateinit var job: Job + override val coroutineContext: CoroutineContext get() = Dispatchers.IO + job + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + (activity as AppCompatActivity).supportActionBar?.show() + job = Job() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_site_permissions_exceptions, container, false) + } + + override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) { + super.onViewCreated(rootView, savedInstanceState) + bindEmptyContainerMess(rootView) + bindClearButton(rootView) + bindRecyclerView(rootView) + } + + private fun bindRecyclerView(rootView: View) { + recyclerView = rootView.findViewById(R.id.exceptions) + recyclerView.layoutManager = LinearLayoutManager(requireContext()) + + val sitePermissionsPaged = requireContext().components.storage.getSitePermissionsPaged() + + val adapter = ExceptionsAdapter(this) + val liveData = LivePagedListBuilder(sitePermissionsPaged, MAX_ITEMS_PER_PAGE).build() + + liveData.observe(this, Observer> { + if (it.isEmpty()) { + showEmptyListMessage() + } else { + hideEmptyListMessage() + adapter.submitList(it) + recyclerView.adapter = adapter + } + }) + } + + private fun hideEmptyListMessage() { + emptyContainerMessage.visibility = GONE + recyclerView.visibility = VISIBLE + clearButton.visibility = VISIBLE + } + + private fun showEmptyListMessage() { + emptyContainerMessage.visibility = VISIBLE + recyclerView.visibility = GONE + clearButton.visibility = GONE + } + + private fun bindEmptyContainerMess(rootView: View) { + emptyContainerMessage = rootView.findViewById(R.id.empty_exception_container) + } + + private fun bindClearButton(rootView: View) { + clearButton = rootView.findViewById(R.id.delete_all_site_permissions_button) + clearButton.setOnClickListener { + requireContext().alert( + R.string.confirm_clear_permissions_on_all_sites, + R.string.clear_permissions + ) { + yesButton { + deleteAllSitePermissions() + } + noButton { } + }.show() + } + } + + override fun onDestroy() { + super.onDestroy() + job.cancel() + } + + private fun deleteAllSitePermissions() { + launch(IO) { + requireContext().components.storage.deleteAllSitePermissions() + launch(Main) { + showEmptyListMessage() + } + } + } + + override fun onClick(view: View?) { + val sitePermissions = view?.tag as SitePermissions + val directions = SitePermissionsExceptionsFragmentDirections + .actionSitePermissionsToExceptionsToSitePermissionsDetails(sitePermissions) + Navigation.findNavController(requireNotNull(view)).navigate(directions) + } +} + +class SitePermissionsViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView) + +class ExceptionsAdapter(private val clickListener: View.OnClickListener) : + PagedListAdapter(diffCallback), CoroutineScope { + private lateinit var job: Job + + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main + job + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + job = Job() + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + super.onDetachedFromRecyclerView(recyclerView) + job.cancel() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SitePermissionsViewHolder { + val context = parent.context + val inflater = LayoutInflater.from(context) + val textView = inflater.inflate(R.layout.fragment_site_permissions_exceptions_item, parent, false) as TextView + return SitePermissionsViewHolder(textView) + } + + override fun onBindViewHolder(holder: SitePermissionsViewHolder, position: Int) { + val sitePermissions = requireNotNull(getItem(position)) + val context = holder.textView.context + val client = context.components.core.client + + launch(IO) { + val bitmap = BrowserIcons(context, client) + .loadIcon(IconRequest(sitePermissions.origin)).await().bitmap + launch(Main) { + val drawable = BitmapDrawable(context.resources, bitmap) + holder.textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) + } + } + holder.textView.text = sitePermissions.origin + holder.textView.tag = sitePermissions + holder.textView.setOnClickListener(clickListener) + } + + companion object { + + private val diffCallback = object : + DiffUtil.ItemCallback() { + override fun areItemsTheSame(old: SitePermissions, new: SitePermissions) = old.origin == new.origin + override fun areContentsTheSame(old: SitePermissions, new: SitePermissions) = old == new + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsFragment.kt index 4805d6103..92b024810 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsFragment.kt @@ -38,6 +38,18 @@ class SitePermissionsFragment : PreferenceFragmentCompat() { private fun setupPreferences() { bindCategoryPhoneFeatures() + bindExceptions() + } + + private fun bindExceptions() { + val keyExceptions = getString(R.string.pref_key_show_site_exceptions) + val exceptionsCategory = requireNotNull(findPreference(keyExceptions)) + + exceptionsCategory.onPreferenceClickListener = OnPreferenceClickListener { + val directions = SitePermissionsFragmentDirections.actionSitePermissionsToExceptions() + Navigation.findNavController(view!!).navigate(directions) + true + } } private fun bindCategoryPhoneFeatures() { @@ -66,7 +78,7 @@ class SitePermissionsFragment : PreferenceFragmentCompat() { } private fun initPhoneFeature(phoneFeature: PhoneFeature, summary: String) { - val keyPreference = getPreferenceKeyBy(phoneFeature) + val keyPreference = phoneFeature.getPreferenceKey(requireContext()) val cameraPhoneFeatures: Preference = requireNotNull(findPreference(keyPreference)) cameraPhoneFeatures.summary = summary @@ -76,15 +88,6 @@ class SitePermissionsFragment : PreferenceFragmentCompat() { } } - private fun getPreferenceKeyBy(phoneFeature: PhoneFeature): String { - return when (phoneFeature) { - CAMERA -> getString(R.string.pref_key_phone_feature_camera) - LOCATION -> getString(R.string.pref_key_phone_feature_location) - MICROPHONE -> getString(R.string.pref_key_phone_feature_microphone) - NOTIFICATION -> getString(R.string.pref_key_phone_feature_notification) - } - } - private fun navigateToPhoneFeature(phoneFeature: PhoneFeature) { val directions = SitePermissionsFragmentDirections.actionSitePermissionsToManagePhoneFeatures(phoneFeature.id) Navigation.findNavController(view!!).navigate(directions) diff --git a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManageExceptionsPhoneFeatureFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManageExceptionsPhoneFeatureFragment.kt new file mode 100644 index 000000000..f49561c1d --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManageExceptionsPhoneFeatureFragment.kt @@ -0,0 +1,177 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.settings + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.RadioButton +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import mozilla.components.feature.sitepermissions.SitePermissions +import mozilla.components.feature.sitepermissions.SitePermissions.Status.ALLOWED +import mozilla.components.feature.sitepermissions.SitePermissions.Status.BLOCKED +import org.jetbrains.anko.alert +import org.jetbrains.anko.noButton +import org.jetbrains.anko.yesButton +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.utils.Settings +import kotlin.coroutines.CoroutineContext + +@SuppressWarnings("TooManyFunctions") +class SitePermissionsManageExceptionsPhoneFeatureFragment : Fragment(), CoroutineScope { + private lateinit var phoneFeature: PhoneFeature + private lateinit var sitePermissions: SitePermissions + private lateinit var radioAllow: RadioButton + private lateinit var radioBlock: RadioButton + private lateinit var blockedByAndroidView: View + private lateinit var job: Job + val settings by lazy { Settings.getInstance(requireContext()) } + + override val coroutineContext: CoroutineContext get() = Dispatchers.IO + job + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + phoneFeature = SitePermissionsManageExceptionsPhoneFeatureFragmentArgs + .fromBundle(requireArguments()) + .phoneFeatureId.toPhoneFeature() + + sitePermissions = SitePermissionsManageExceptionsPhoneFeatureFragmentArgs + .fromBundle(requireArguments()) + .sitePermissions + + (activity as AppCompatActivity).title = phoneFeature.getLabel(requireContext()) + (activity as AppCompatActivity).supportActionBar?.show() + job = Job() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val rootView = + inflater.inflate(R.layout.fragment_manage_site_permissions_exceptions_feature_phone, container, false) + + initAskToAllowRadio(rootView) + initBlockRadio(rootView) + bindBlockedByAndroidContainer(rootView) + initClearPermissionsButton(rootView) + + return rootView + } + + override fun onResume() { + super.onResume() + initBlockedByAndroidView(phoneFeature, blockedByAndroidView) + } + + override fun onDestroy() { + super.onDestroy() + job.cancel() + } + + private fun initAskToAllowRadio(rootView: View) { + radioAllow = rootView.findViewById(R.id.ask_to_allow_radio) + val askToAllowText = getString(R.string.preference_option_phone_feature_allowed) + + radioAllow.text = askToAllowText + + radioAllow.setOnClickListener { + updatedSitePermissions(ALLOWED) + } + radioAllow.restoreState(ALLOWED) + } + + private fun RadioButton.restoreState(status: SitePermissions.Status) { + if (phoneFeature.getStatus(sitePermissions) == status) { + this.isChecked = true + } + } + + private fun initBlockRadio(rootView: View) { + radioBlock = rootView.findViewById(R.id.block_radio) + radioBlock.setOnClickListener { + updatedSitePermissions(BLOCKED) + } + radioBlock.restoreState(BLOCKED) + } + + private fun initClearPermissionsButton(rootView: View) { + val button = rootView.findViewById