1
0
Fork 0

For #1745 - Allow trackers from Allow List (#2310)

master
Emily Kager 2019-05-07 13:28:38 -07:00 committed by Colin Lee
parent 79fcb97ed2
commit 84a7430e57
20 changed files with 676 additions and 32 deletions

View File

@ -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 {

View File

@ -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()
)

View File

@ -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<String>? = 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<String> {
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<String>) {
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<String>()
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<String>) {
save(context, load(context) - domains)
}
private fun preferences(context: Context): SharedPreferences =
context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)
}

View File

@ -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<ExceptionsItem>) {
val items: List<AdapterItem>
init {
val items = mutableListOf<AdapterItem>()
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<ExceptionsAction>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var exceptionsList: ExceptionsList = ExceptionsList(emptyList())
private lateinit var job: Job
fun updateData(items: List<ExceptionsItem>) {
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()
}
}

View File

@ -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<ExceptionsState, ExceptionsAction, ExceptionsChange>(
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<ExceptionsItem>) : 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<ExceptionsItem>) : ExceptionsChange()
}

View File

@ -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<ExceptionsAction>()
.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<ExceptionsItem> {
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<ExceptionsChange>().onNext(ExceptionsChange.Change(items))
}
}
}
}

View File

@ -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<ExceptionsAction>,
changesObservable: Observable<ExceptionsChange>
) :
UIView<ExceptionsState, ExceptionsAction, ExceptionsChange>(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<ExceptionsState> {
(view.exceptions_list.adapter as ExceptionsAdapter).updateData(it.items)
}
}

View File

@ -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<ExceptionsAction>
) : 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
}
}

View File

@ -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
}
}

View File

@ -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<ExceptionsAction>,
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
}
}

View File

@ -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<Preference>(trackingProtectionKey)
val preferenceTP = findPreference<SwitchPreference>(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<Preference>(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
}
}
}

View File

@ -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<QuickSettingsChange>().onNext(
QuickSettingsChange.Change(

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/exceptions_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/exceptions_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<Button android:id="@+id/removeAllExceptions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_margin="12dp"
android:backgroundTint="?attr/neutral"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:text="@string/preferences_tracking_protection_exceptions_turn_on_for_all"
android:textAllCaps="false"
android:textColor="?attr/accentHighContrast"
android:textSize="14sp"
android:textStyle="bold"
xmlns:android="http://schemas.android.com/apk/res/android" />

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="@+id/favicon_image"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:adjustViewBounds="true"
android:importantForAccessibility="no"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/domainView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_weight="1"
android:ellipsize="middle"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?primaryText"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/delete_exception"
app:layout_constraintStart_toEndOf="@+id/favicon_image"
app:layout_constraintTop_toTopOf="parent"
tools:text="mozilla.org" />
<ImageButton
android:id="@+id/delete_exception"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/exceptions_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:text="@string/preferences_tracking_protection_exceptions_description"
android:textColor="?primaryText"
android:textSize="16sp" />

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/exceptions_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="org.mozilla.fenix.exceptions.ExceptionsFragment">
</LinearLayout>

View File

@ -290,5 +290,13 @@
<fragment
android:id="@+id/trackingProtectionFragment"
android:name="org.mozilla.fenix.settings.TrackingProtectionFragment"
android:label="TrackingProtectionFragment" />
android:label="TrackingProtectionFragment" >
<action
android:id="@+id/action_trackingProtectionFragment_to_exceptionsFragment"
app:destination="@id/exceptionsFragment" />
</fragment>
<fragment
android:id="@+id/exceptionsFragment"
android:name="org.mozilla.fenix.exceptions.ExceptionsFragment"
android:label="ExceptionsFragment" />
</navigation>

View File

@ -167,6 +167,10 @@
<string name="preferences_tracking_protection_description">Block content and scripts that track you online</string>
<!-- Preference for tracking protection exceptions -->
<string name="preferences_tracking_protection_exceptions">Exceptions</string>
<!-- Preference description for tracking protection exceptions -->
<string name="preferences_tracking_protection_exceptions_description">Tracking Protection is off for these websites</string>
<!-- Button in Exceptions Preference to turn on tracking protection for all sites (remove all exceptions) -->
<string name="preferences_tracking_protection_exceptions_turn_on_for_all">Turn on for all sites</string>
<!-- Preference switch for Telemetry -->
<string name="preferences_telemetry">Telemetry</string>

View File

@ -2,8 +2,7 @@
<!-- 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/. -->
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreference
android:defaultValue="true"
android:icon="@drawable/ic_tracking_protection"
@ -11,7 +10,6 @@
android:summary="@string/preferences_tracking_protection_description"
android:title="@string/preferences_tracking_protection" />
<Preference
app:isPreferenceVisible="false"
android:icon="@drawable/ic_internet"
android:key="@string/pref_key_tracking_protection_exceptions"
android:title="@string/preferences_tracking_protection_exceptions" />