diff --git a/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt b/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt index dca2e40bd..cc46171cc 100644 --- a/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt +++ b/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt @@ -9,14 +9,45 @@ import mozilla.components.browser.errorpages.ErrorPages import mozilla.components.browser.errorpages.ErrorType import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.request.RequestInterceptor +import org.mozilla.fenix.exceptions.ExceptionDomains import org.mozilla.fenix.ext.components +import org.mozilla.fenix.utils.Settings +import java.net.MalformedURLException +import java.net.URL class AppRequestInterceptor(private val context: Context) : RequestInterceptor { override fun onLoadRequest(session: EngineSession, uri: String): RequestInterceptor.InterceptionResponse? { + adjustTrackingProtection(uri, context) // Accounts uses interception to check for a "success URL" in the sign-in flow to finalize authentication. return context.components.services.accountsAuthFeature.interceptor.onLoadRequest(session, uri) } + private fun adjustTrackingProtection(url: String, context: Context) { + val host = try { + URL(url).host + } catch (e: MalformedURLException) { + url + } + val trackingProtectionException = ExceptionDomains.load(context).contains(host) + val trackingProtectionEnabled = Settings.getInstance(context).shouldUseTrackingProtection + val core = context.components.core + if (trackingProtectionException || !trackingProtectionEnabled) { + with(core.sessionManager) { + selectedSession?.let { + getEngineSession(it)?.disableTrackingProtection() + } + } + } else { + val policy = core.createTrackingProtectionPolicy(normalMode = true) + core.engine.settings.trackingProtectionPolicy = policy + with(core.sessionManager) { + selectedSession?.let { + getEngineSession(it)?.enableTrackingProtection(policy) + } + } + } + } + override fun onErrorRequest( session: EngineSession, errorType: ErrorType, @@ -27,8 +58,10 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor { val htmlResource = getPageForRiskLevel(riskLevel) val cssResource = getStyleForRiskLevel(riskLevel) - return RequestInterceptor.ErrorResponse(ErrorPages - .createErrorPage(context, errorType, uri = uri, htmlResource = htmlResource, cssResource = cssResource)) + return RequestInterceptor.ErrorResponse( + ErrorPages + .createErrorPage(context, errorType, uri = uri, htmlResource = htmlResource, cssResource = cssResource) + ) } private fun getPageForRiskLevel(riskLevel: RiskLevel): Int { diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index 90106d601..f18d41fdb 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -667,7 +667,7 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope, val quickSettingsSheet = QuickSettingsSheetDialogFragment.newInstance( url = session.url, isSecured = session.securityInfo.secure, - isTrackingProtectionOn = Settings.getInstance(context!!).shouldUseTrackingProtection, + isTrackingProtectionOn = session.trackerBlockingEnabled, sitePermissions = sitePermissions, gravity = getAppropriateLayoutGravity() ) diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionDomains.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionDomains.kt new file mode 100644 index 000000000..8ff20e8cd --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionDomains.kt @@ -0,0 +1,78 @@ +package org.mozilla.fenix.exceptions + +/* 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/. */ + +import android.content.Context +import android.content.SharedPreferences + +/** + * Contains functionality to manage custom domains for allow-list. + */ +object ExceptionDomains { + private const val PREFERENCE_NAME = "exceptions" + private const val KEY_DOMAINS = "exceptions_domains" + private const val SEPARATOR = "@<;>@" + + private var exceptions: List? = null + + /** + * Loads the previously added/saved custom domains from preferences. + * + * @param context the application context + * @return list of custom domains + */ + fun load(context: Context): List { + if (exceptions == null) { + exceptions = (preferences(context) + .getString(KEY_DOMAINS, "") ?: "") + .split(SEPARATOR) + .filter { !it.isEmpty() } + } + + return exceptions ?: listOf() + } + + /** + * Saves the provided domains to preferences. + * + * @param context the application context + * @param domains list of domains + */ + fun save(context: Context, domains: List) { + exceptions = domains + + preferences(context) + .edit() + .putString(KEY_DOMAINS, domains.joinToString(separator = SEPARATOR)) + .apply() + } + + /** + * Adds the provided domain to preferences. + * + * @param context the application context + * @param domain the domain to add + */ + fun add(context: Context, domain: String) { + val domains = mutableListOf() + domains.addAll(load(context)) + domains.add(domain) + + save(context, domains) + } + + /** + * Removes the provided domain from preferences. + * + * @param context the application context + * @param domains the domain to remove + */ + fun remove(context: Context, domains: List) { + save(context, load(context) - domains) + } + + private fun preferences(context: Context): SharedPreferences = + context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE) +} diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt new file mode 100644 index 000000000..807dbb536 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt @@ -0,0 +1,84 @@ +/* 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.exceptions + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.reactivex.Observer +import kotlinx.coroutines.Job +import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder +import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder +import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder + +private sealed class AdapterItem { + object DeleteButton : AdapterItem() + object Header : AdapterItem() + data class Item(val item: ExceptionsItem) : AdapterItem() +} + +private class ExceptionsList(val exceptions: List) { + val items: List + init { + val items = mutableListOf() + items.add(AdapterItem.Header) + for (exception in exceptions) { + items.add(AdapterItem.Item(exception)) + } + items.add(AdapterItem.DeleteButton) + this.items = items + } +} + +class ExceptionsAdapter( + private val actionEmitter: Observer +) : RecyclerView.Adapter() { + private var exceptionsList: ExceptionsList = ExceptionsList(emptyList()) + private lateinit var job: Job + + fun updateData(items: List) { + this.exceptionsList = ExceptionsList(items) + notifyDataSetChanged() + } + + override fun getItemCount(): Int = exceptionsList.items.size + + override fun getItemViewType(position: Int): Int { + return when (exceptionsList.items[position]) { + is AdapterItem.DeleteButton -> ExceptionsDeleteButtonViewHolder.LAYOUT_ID + is AdapterItem.Header -> ExceptionsHeaderViewHolder.LAYOUT_ID + is AdapterItem.Item -> ExceptionsListItemViewHolder.LAYOUT_ID + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) + + return when (viewType) { + ExceptionsDeleteButtonViewHolder.LAYOUT_ID -> ExceptionsDeleteButtonViewHolder(view, actionEmitter) + ExceptionsHeaderViewHolder.LAYOUT_ID -> ExceptionsHeaderViewHolder(view) + ExceptionsListItemViewHolder.LAYOUT_ID -> ExceptionsListItemViewHolder(view, actionEmitter, job) + else -> throw IllegalStateException() + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is ExceptionsListItemViewHolder -> (exceptionsList.items[position] as AdapterItem.Item).also { + holder.bind(it.item) + } + } + } + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + job = Job() + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + super.onDetachedFromRecyclerView(recyclerView) + job.cancel() + } +} diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsComponent.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsComponent.kt new file mode 100644 index 000000000..b8feb03fd --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsComponent.kt @@ -0,0 +1,51 @@ +/* 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.exceptions + +import android.view.ViewGroup +import org.mozilla.fenix.mvi.Action +import org.mozilla.fenix.mvi.ActionBusFactory +import org.mozilla.fenix.mvi.Change +import org.mozilla.fenix.mvi.UIComponent +import org.mozilla.fenix.mvi.ViewState +import org.mozilla.fenix.test.Mockable + +data class ExceptionsItem(val url: String) + +@Mockable +class ExceptionsComponent( + private val container: ViewGroup, + bus: ActionBusFactory, + override var initialState: ExceptionsState = ExceptionsState(emptyList()) +) : + UIComponent( + bus.getManagedEmitter(ExceptionsAction::class.java), + bus.getSafeManagedObservable(ExceptionsChange::class.java) + ) { + + override val reducer: (ExceptionsState, ExceptionsChange) -> ExceptionsState = { state, change -> + when (change) { + is ExceptionsChange.Change -> state.copy(items = change.list) + } + } + + override fun initView() = ExceptionsUIView(container, actionEmitter, changesObservable) + + init { + render(reducer) + } +} + +data class ExceptionsState(val items: List) : ViewState + +sealed class ExceptionsAction : Action { + sealed class Delete : ExceptionsAction() { + object All : Delete() + data class One(val item: ExceptionsItem) : Delete() + } +} + +sealed class ExceptionsChange : Change { + data class Change(val list: List) : ExceptionsChange() +} diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt new file mode 100644 index 000000000..b24d6aadf --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt @@ -0,0 +1,99 @@ +package org.mozilla.fenix.exceptions + +/* 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/. */ + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.navigation.Navigation +import kotlinx.android.synthetic.main.fragment_exceptions.view.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import org.mozilla.fenix.R +import org.mozilla.fenix.mvi.ActionBusFactory +import org.mozilla.fenix.mvi.getAutoDisposeObservable +import org.mozilla.fenix.mvi.getManagedEmitter +import kotlin.coroutines.CoroutineContext + +class ExceptionsFragment : Fragment(), CoroutineScope { + private var job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + private lateinit var exceptionsComponent: ExceptionsComponent + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + job = Job() + } + + override fun onResume() { + super.onResume() + activity?.title = getString(R.string.preference_exceptions) + (activity as AppCompatActivity).supportActionBar?.show() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_exceptions, container, false) + exceptionsComponent = ExceptionsComponent( + view.exceptions_layout, + ActionBusFactory.get(this), + ExceptionsState(loadAndMapExceptions()) + ) + return view + } + + override fun onStart() { + super.onStart() + getAutoDisposeObservable() + .subscribe { + when (it) { + is ExceptionsAction.Delete.All -> launch(Dispatchers.IO) { + val domains = ExceptionDomains.load(context!!) + ExceptionDomains.remove(context!!, domains) + launch(Dispatchers.Main) { + view?.let { view: View -> Navigation.findNavController(view).navigateUp() } + } + } + is ExceptionsAction.Delete.One -> launch(Dispatchers.IO) { + ExceptionDomains.remove(context!!, listOf(it.item.url)) + reloadData() + } + } + } + } + + private fun loadAndMapExceptions(): List { + return ExceptionDomains.load(context!!) + .map { item -> + ExceptionsItem( + item + ) + } + } + + private suspend fun reloadData() { + val items = loadAndMapExceptions() + + coroutineScope { + launch(Dispatchers.Main) { + if (items.isEmpty()) { + view?.let { view: View -> Navigation.findNavController(view).navigateUp() } + return@launch + } + getManagedEmitter().onNext(ExceptionsChange.Change(items)) + } + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsUIView.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsUIView.kt new file mode 100644 index 000000000..d5cbd8233 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsUIView.kt @@ -0,0 +1,39 @@ +/* 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.exceptions + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.recyclerview.widget.LinearLayoutManager +import io.reactivex.Observable +import io.reactivex.Observer +import io.reactivex.functions.Consumer +import kotlinx.android.synthetic.main.component_exceptions.view.* +import org.mozilla.fenix.R +import org.mozilla.fenix.mvi.UIView + +class ExceptionsUIView( + container: ViewGroup, + actionEmitter: Observer, + changesObservable: Observable +) : + UIView(container, actionEmitter, changesObservable) { + + override val view: LinearLayout = LayoutInflater.from(container.context) + .inflate(R.layout.component_exceptions, container, true) + .findViewById(R.id.exceptions_wrapper) + + init { + view.exceptions_list.apply { + adapter = ExceptionsAdapter(actionEmitter) + layoutManager = LinearLayoutManager(container.context) + } + } + + override fun updateView() = Consumer { + (view.exceptions_list.adapter as ExceptionsAdapter).updateData(it.items) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt b/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt new file mode 100644 index 000000000..77b398ffa --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt @@ -0,0 +1,29 @@ +/* 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.exceptions.viewholders + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import io.reactivex.Observer +import kotlinx.android.synthetic.main.delete_exceptions_button.view.* +import org.mozilla.fenix.R +import org.mozilla.fenix.exceptions.ExceptionsAction + +class ExceptionsDeleteButtonViewHolder( + view: View, + private val actionEmitter: Observer +) : RecyclerView.ViewHolder(view) { + private val deleteButton = view.removeAllExceptions + + init { + deleteButton.setOnClickListener { + actionEmitter.onNext(ExceptionsAction.Delete.All) + } + } + + companion object { + const val LAYOUT_ID = R.layout.delete_exceptions_button + } +} diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsHeaderViewHolder.kt b/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsHeaderViewHolder.kt new file mode 100644 index 000000000..322fc659e --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsHeaderViewHolder.kt @@ -0,0 +1,17 @@ +/* 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.exceptions.viewholders + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import org.mozilla.fenix.R + +class ExceptionsHeaderViewHolder( + view: View +) : RecyclerView.ViewHolder(view) { + companion object { + const val LAYOUT_ID = R.layout.exceptions_description + } +} diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolder.kt new file mode 100644 index 000000000..5a79a0a16 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolder.kt @@ -0,0 +1,64 @@ +/* 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.exceptions.viewholders + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import io.reactivex.Observer +import kotlinx.android.synthetic.main.exception_item.view.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import mozilla.components.browser.icons.IconRequest +import org.mozilla.fenix.R +import org.mozilla.fenix.exceptions.ExceptionsAction +import org.mozilla.fenix.exceptions.ExceptionsItem +import org.mozilla.fenix.ext.components +import kotlin.coroutines.CoroutineContext + +class ExceptionsListItemViewHolder( + view: View, + private val actionEmitter: Observer, + val job: Job +) : RecyclerView.ViewHolder(view), CoroutineScope { + + override val coroutineContext: CoroutineContext + get() = Dispatchers.IO + job + + private val favicon = view.favicon_image + private val url = view.domainView + private val deleteButton = view.delete_exception + + private var item: ExceptionsItem? = null + + init { + deleteButton.setOnClickListener { + item?.let { + actionEmitter.onNext(ExceptionsAction.Delete.One(it)) + } + } + } + + fun bind(item: ExceptionsItem) { + this.item = item + url.text = item.url + updateFavIcon(item.url) + } + + private fun updateFavIcon(url: String) { + launch(Dispatchers.IO) { + val bitmap = favicon.context.components.utils.icons + .loadIcon(IconRequest(url)).await().bitmap + launch(Dispatchers.Main) { + favicon.setImageBitmap(bitmap) + } + } + } + + companion object { + const val LAYOUT_ID = R.layout.exception_item + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt index 7ed80c5ba..90ff034a1 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt @@ -6,43 +6,66 @@ 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 androidx.preference.SwitchPreference import org.mozilla.fenix.R +import org.mozilla.fenix.exceptions.ExceptionDomains import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.requireComponents +import org.mozilla.fenix.utils.Settings class TrackingProtectionFragment : PreferenceFragmentCompat() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - (activity as AppCompatActivity).title = getString(R.string.preferences_tracking_protection) - (activity as AppCompatActivity).supportActionBar?.show() - } - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.tracking_protection_preferences, rootKey) } override fun onResume() { super.onResume() + activity?.title = getString(R.string.preferences_tracking_protection) + (activity as AppCompatActivity).supportActionBar?.show() + // Tracking Protection Switch val trackingProtectionKey = context!!.getPreferenceKey(R.string.pref_key_tracking_protection) - val preferenceTP = findPreference(trackingProtectionKey) + val preferenceTP = findPreference(trackingProtectionKey) preferenceTP?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + Settings.getInstance(context!!).setTrackingProtection(newValue = newValue as Boolean) with(requireComponents.core) { val policy = - createTrackingProtectionPolicy(newValue as Boolean) + createTrackingProtectionPolicy(newValue) engine.settings.trackingProtectionPolicy = policy - with(sessionManager) { - sessions.forEach { getEngineSession(it)?.enableTrackingProtection(policy) } + sessions.forEach { + if (newValue) + getEngineSession(it)?.enableTrackingProtection(policy) else + getEngineSession(it)?.disableTrackingProtection() + } } } requireContext().components.useCases.sessionUseCases.reload.invoke() true } + + context?.let { + val exceptionsEmpty = ExceptionDomains.load(it).isEmpty() + val exceptions = + it.getPreferenceKey(R.string.pref_key_tracking_protection_exceptions) + val preferenceExceptions = findPreference(exceptions) + preferenceExceptions.shouldDisableView = true + preferenceExceptions.isEnabled = !exceptionsEmpty + preferenceExceptions?.onPreferenceClickListener = getClickListenerForExceptions() + } + } + + private fun getClickListenerForExceptions(): Preference.OnPreferenceClickListener { + return Preference.OnPreferenceClickListener { + val directions = TrackingProtectionFragmentDirections.actionTrackingProtectionFragmentToExceptionsFragment() + Navigation.findNavController(view!!).navigate(directions) + true + } } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFragment.kt index 10b5bbf8d..ca8f6eefb 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFragment.kt @@ -27,13 +27,15 @@ import mozilla.components.feature.sitepermissions.SitePermissions import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.BrowserFragment +import org.mozilla.fenix.exceptions.ExceptionDomains import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.getAutoDisposeObservable import org.mozilla.fenix.mvi.getManagedEmitter import org.mozilla.fenix.settings.PhoneFeature -import org.mozilla.fenix.utils.Settings +import java.net.MalformedURLException +import java.net.URL import kotlin.coroutines.CoroutineContext private const val KEY_URL = "KEY_URL" @@ -174,19 +176,17 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment(), CoroutineSco private fun arePermissionsGranted(requestCode: Int, grantResults: IntArray) = requestCode == REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS && grantResults.all { it == PERMISSION_GRANTED } - private fun toggleTrackingProtection(trackingEnabled: Boolean, context: Context) { - with(requireComponents.core) { - val policy = - createTrackingProtectionPolicy(trackingEnabled) - Settings.getInstance(context).setTrackingProtection(trackingEnabled) - engine.settings.trackingProtectionPolicy = policy - - with(sessionManager) { - sessions.forEach { - getEngineSession(it)?.enableTrackingProtection( - policy - ) - } + private fun toggleTrackingProtection(context: Context, url: String) { + val host = try { + URL(url).host + } catch (e: MalformedURLException) { + url + } + launch { + if (!ExceptionDomains.load(context).contains(host)) { + ExceptionDomains.add(context, host) + } else { + ExceptionDomains.remove(context, listOf(host)) } } } @@ -209,7 +209,7 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment(), CoroutineSco } is QuickSettingsAction.ToggleTrackingProtection -> { val trackingEnabled = it.trackingProtection - context?.let { toggleTrackingProtection(trackingEnabled, it) } + context?.let { context: Context -> toggleTrackingProtection(context, url) } launch(Dispatchers.Main) { getManagedEmitter().onNext( QuickSettingsChange.Change( diff --git a/app/src/main/res/layout/component_exceptions.xml b/app/src/main/res/layout/component_exceptions.xml new file mode 100644 index 000000000..8dfda78de --- /dev/null +++ b/app/src/main/res/layout/component_exceptions.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/layout/delete_exceptions_button.xml b/app/src/main/res/layout/delete_exceptions_button.xml new file mode 100644 index 000000000..12b15e88b --- /dev/null +++ b/app/src/main/res/layout/delete_exceptions_button.xml @@ -0,0 +1,18 @@ + + +