diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bc9f95bc7..c37a223f29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #176 - Added a swipe to delete gesture on home screen - #1539 - Added bookmarks multi-select related features - #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 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 f1ccccb84f..b30138ac62 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -82,6 +82,7 @@ import kotlin.coroutines.CoroutineContext class BrowserFragment : Fragment(), BackHandler, CoroutineScope { private lateinit var toolbarComponent: ToolbarComponent + private var sessionObserver: Session.Observer? = null private val sessionFeature = ViewBoundFeatureWrapper() private val contextMenuFeature = ViewBoundFeatureWrapper() private val downloadsFeature = ViewBoundFeatureWrapper() @@ -130,14 +131,6 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope { ) (layoutParams as CoordinatorLayout.LayoutParams).apply { - // Stop toolbar from collapsing if TalkBack is enabled - val accessibilityManager = context - ?.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager - - if (!accessibilityManager.isTouchExplorationEnabled) { - behavior = BrowserToolbarBottomBehavior(view.context, null) - } - gravity = Gravity.BOTTOM height = (resources.displayMetrics.density * TOOLBAR_HEIGHT).toInt() } @@ -289,6 +282,7 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope { @Suppress("ComplexMethod") override fun onStart() { super.onStart() + sessionObserver = subscribeToSession() getAutoDisposeObservable() .subscribe { when (it) { @@ -370,6 +364,13 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope { assignSitePermissionsRules() } + override fun onStop() { + super.onStop() + sessionObserver?.let { + requireComponents.core.sessionManager.selectedSession?.unregister(it) + } + } + override fun onBackPressed(): Boolean { return when { findInPageIntegration.onBackPressed() -> true @@ -520,6 +521,34 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope { clipBoard.primaryClip = ClipData.newRawUri("Uri", uri) } + private fun subscribeToSession(): Session.Observer { + val observer = object : Session.Observer { + override fun onLoadingStateChanged(session: Session, loading: Boolean) { + super.onLoadingStateChanged(session, loading) + setToolbarBehavior(loading) + } + } + requireComponents.core.sessionManager.selectedSession?.register(observer) + return observer + } + + private fun setToolbarBehavior(loading: Boolean) { + val toolbarView = toolbarComponent.uiView.view + (toolbarView.layoutParams as CoordinatorLayout.LayoutParams).apply { + // Stop toolbar from collapsing if TalkBack is enabled or page is loading + val accessibilityManager = context + ?.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager + if (!accessibilityManager.isTouchExplorationEnabled) { + if (!loading) { + behavior = BrowserToolbarBottomBehavior(context, null) + } else { + (behavior as? BrowserToolbarBottomBehavior)?.forceExpand(toolbarView) + behavior = null + } + } + } + } + companion object { private const val REQUEST_CODE_DOWNLOAD_PERMISSIONS = 1 private const val REQUEST_CODE_PROMPT_PERMISSIONS = 2 diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserToolbarBottomBehavior.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserToolbarBottomBehavior.kt index a76e3d56c5..8b19063990 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserToolbarBottomBehavior.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserToolbarBottomBehavior.kt @@ -44,6 +44,10 @@ class BrowserToolbarBottomBehavior( duration = SNAP_ANIMATION_DURATION } + fun forceExpand(view: View) { + animateSnap(view, SnapDirection.UP) + } + override fun onStartNestedScroll( coordinatorLayout: CoordinatorLayout, child: BrowserToolbar, diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index 18b059b756..bd2d4a1242 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -5,10 +5,8 @@ package org.mozilla.fenix.components import android.content.Context -import android.content.SharedPreferences import android.content.res.Configuration import android.os.Bundle -import android.preference.PreferenceManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async @@ -60,13 +58,11 @@ class Core(private val context: Context) { * configuration (see build variants). */ val engine: Engine by lazy { - val prefs = PreferenceManager.getDefaultSharedPreferences(context) - val defaultSettings = DefaultSettings( requestInterceptor = AppRequestInterceptor(context), remoteDebuggingEnabled = Settings.getInstance(context).isRemoteDebuggingEnabled, testingModeEnabled = false, - trackingProtectionPolicy = createTrackingProtectionPolicy(prefs), + trackingProtectionPolicy = createTrackingProtectionPolicy(), historyTrackingDelegate = HistoryDelegate(historyStorage) ) @@ -133,17 +129,14 @@ class Core(private val context: Context) { /** * Constructs a [TrackingProtectionPolicy] based on current preferences. * - * @param prefs the shared preferences to use when reading tracking - * protection settings. * @param normalMode whether or not tracking protection should be enabled * in normal browsing mode, defaults to the current preference value. * @param privateMode whether or not tracking protection should be enabled * in private browsing mode, default to the current preference value. * @return the constructed tracking protection policy based on preferences. */ - fun createTrackingProtectionPolicy( - prefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context), - normalMode: Boolean = true, + private fun createTrackingProtectionPolicy( + normalMode: Boolean = Settings.getInstance(context).shouldUseTrackingProtection, privateMode: Boolean = true ): TrackingProtectionPolicy { val trackingProtectionPolicy = TrackingProtectionPolicy.select( @@ -160,6 +153,11 @@ class Core(private val context: Context) { } } + fun updateTrackingProtection(newValue: Boolean) { + engine.settings.trackingProtectionPolicy = + createTrackingProtectionPolicy(normalMode = newValue) + } + /** * Sets Preferred Color scheme based on Dark/Light Theme Settings or Current Configuration */ diff --git a/app/src/main/java/org/mozilla/fenix/ext/CoroutineScope.kt b/app/src/main/java/org/mozilla/fenix/ext/CoroutineScope.kt new file mode 100644 index 0000000000..c22f01d243 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/ext/CoroutineScope.kt @@ -0,0 +1,26 @@ +/* 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.ext + +import android.view.View +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.mozilla.fenix.components.FenixSnackbar + +fun CoroutineScope.allowUndo(view: View, message: String, undoActionTitle: String, operation: suspend () -> Unit) { + val undoJob = launch(Dispatchers.IO) { + delay(UNDO_DELAY) + operation.invoke() + } + FenixSnackbar.make(view, FenixSnackbar.LENGTH_LONG) + .setText(message) + .setAction(undoActionTitle) { + undoJob.cancel() + }.show() +} + +internal const val UNDO_DELAY = 3000L diff --git a/app/src/main/java/org/mozilla/fenix/ext/String.kt b/app/src/main/java/org/mozilla/fenix/ext/String.kt index 04421f26f9..9e1d127e4b 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/String.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/String.kt @@ -6,6 +6,9 @@ package org.mozilla.fenix.ext +import java.net.MalformedURLException +import java.net.URL + /** * Replaces the keys with the values with the map provided. */ @@ -14,3 +17,12 @@ fun String.replace(pairs: Map): String { pairs.forEach { (l, r) -> result = result.replace(l, r) } return result } + +fun String?.urlToHost(): String { + return try { + val url = URL(this) + url.host + } catch (e: MalformedURLException) { + "" + } +} diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt index fbd13005be..87933d74c6 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt @@ -31,7 +31,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import mozilla.appservices.places.BookmarkRoot import mozilla.components.concept.storage.BookmarkNode @@ -45,9 +44,11 @@ import org.mozilla.fenix.BrowsingModeManager import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar +import org.mozilla.fenix.ext.allowUndo import org.mozilla.fenix.ext.getColorFromAttr import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.share +import org.mozilla.fenix.ext.urlToHost import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.getAutoDisposeObservable import org.mozilla.fenix.mvi.getManagedEmitter @@ -216,7 +217,10 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve } } is BookmarkAction.Delete -> { - launch(IO) { + allowUndo( + view!!, getString(R.string.bookmark_deletion_snackbar_message, it.item.url.urlToHost()), + getString(R.string.bookmark_undo_deletion) + ) { requireComponents.core.bookmarksStorage.deleteNode(it.item.guid) refreshBookmarks() } @@ -277,16 +281,13 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve true } R.id.delete_bookmarks_multi_select -> { - val deleteJob = launch(IO) { - delay(bookmarkDeletionDelay) + allowUndo( + view!!, getString(R.string.bookmark_deletion_multiple_snackbar_message), + getString(R.string.bookmark_undo_deletion) + ) { deleteSelectedBookmarks() refreshBookmarks() } - FenixSnackbar.make(view!!, FenixSnackbar.LENGTH_LONG) - .setText(getString(R.string.bookmark_deletion_multiple_snackbar_message)) - .setAction(getString(R.string.bookmark_undo_deletion)) { - deleteJob.cancel() - }.show() true } else -> super.onOptionsItemSelected(item) @@ -344,8 +345,4 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve val uri = Uri.parse(url) clipBoard.primaryClip = ClipData.newRawUri("Uri", uri) } - - companion object { - private const val bookmarkDeletionDelay = 3000L - } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index 8f7ae35b3e..e281337cc4 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -47,6 +47,7 @@ import org.mozilla.fenix.R.string.pref_key_theme import org.mozilla.fenix.R.string.pref_key_account import org.mozilla.fenix.R.string.pref_key_account_category import org.mozilla.fenix.R.string.pref_key_search_engine_settings +import org.mozilla.fenix.R.string.pref_key_tracking_protection_settings import org.mozilla.fenix.utils.ItsNotBrokenSnack @SuppressWarnings("TooManyFunctions") @@ -99,6 +100,9 @@ class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope, AccountObse resources.getString(pref_key_search_engine_settings) -> { navigateToSearchEngineSettings() } + resources.getString(pref_key_tracking_protection_settings) -> { + navigateToTrackingProtectionSettings() + } resources.getString(pref_key_site_permissions) -> { navigateToSitePermissions() } @@ -221,6 +225,11 @@ class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope, AccountObse Navigation.findNavController(view!!).navigate(directions) } + private fun navigateToTrackingProtectionSettings() { + val directions = SettingsFragmentDirections.actionSettingsFragmentToTrackingProtectionFragment() + Navigation.findNavController(view!!).navigate(directions) + } + private fun navigateToThemeSettings() { val directions = SettingsFragmentDirections.actionSettingsFragmentToThemeFragment() Navigation.findNavController(view!!).navigate(directions) diff --git a/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt new file mode 100644 index 0000000000..72f61f7bbc --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.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.settings + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.getPreferenceKey +import org.mozilla.fenix.ext.requireComponents + +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() + // Tracking Protection Switch + val trackingProtectionKey = + context!!.getPreferenceKey(R.string.pref_key_tracking_protection) + val preferenceTP = findPreference(trackingProtectionKey) + preferenceTP?.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _, newValue -> + requireComponents.core.updateTrackingProtection(newValue as Boolean) + true + } + + // Exceptions + val exceptions = + context!!.getPreferenceKey(R.string.pref_key_tracking_protection_exceptions) + val preferenceExceptions = findPreference(exceptions) + preferenceExceptions?.onPreferenceClickListener = getClickListenerForSignOut() + } + + private fun getClickListenerForSignOut(): Preference.OnPreferenceClickListener { + return Preference.OnPreferenceClickListener { + // TODO go to Exceptions Fragment + true + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 95979d9d50..63c2134ceb 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -79,6 +79,12 @@ class Settings private constructor(context: Context) { false ) + val shouldUseTrackingProtection: Boolean + get() = preferences.getBoolean( + appContext.getPreferenceKey(R.string.pref_key_tracking_protection), + true + ) + val shouldUseAutoBatteryTheme: Boolean get() = preferences.getBoolean( appContext.getPreferenceKey(R.string.pref_key_auto_battery_theme), diff --git a/app/src/main/res/drawable/ic_tracking_protection.xml b/app/src/main/res/drawable/ic_tracking_protection.xml new file mode 100644 index 0000000000..7600a698b4 --- /dev/null +++ b/app/src/main/res/drawable/ic_tracking_protection.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index cb22f0159d..2c867b8a3e 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -232,6 +232,9 @@ + @@ -277,4 +280,8 @@ android:id="@+id/themeFragment" android:name="org.mozilla.fenix.settings.ThemeFragment" android:label="ThemeFragment" /> + \ No newline at end of file diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 05b4219525..48894fadb5 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -56,4 +56,9 @@ pref_key_dark_theme pref_key_auto_battery_theme pref_key_follow_device_theme + + + pref_key_tracking_protection_settings + pref_key_tracking_protection + pref_key_tracking_protection_exceptions diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 31874f1e01..097f774d8c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -156,6 +156,15 @@ Last synced: never + + Tracking Protection + + Tracking Protection + + Block content and scripts that track you online + + Exceptions + Telemetry @@ -327,7 +336,8 @@ Invalid URL No bookmarks here - + Deleted %1$s Deleting selected bookmarks diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 50c166e252..a048f31e3f 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -55,6 +55,10 @@ + + + + + +