1
0
Fork 0
fenix/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsController.kt

270 lines
11 KiB
Kotlin

/* 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.quicksettings
import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.navigation.NavController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.launch
import mozilla.components.browser.session.Session
import mozilla.components.feature.session.SessionUseCases.ReloadUrlUseCase
import mozilla.components.feature.session.TrackingProtectionUseCases
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.tabs.TabsUseCases.AddNewTabUseCase
import mozilla.components.support.base.feature.OnNeedToRequestPermissions
import org.mozilla.fenix.browser.BrowserFragment
import org.mozilla.fenix.components.PermissionStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled
import org.mozilla.fenix.settings.toggle
import org.mozilla.fenix.utils.Settings
/**
* [QuickSettingsSheetDialogFragment] controller.
*
* Delegated by View Interactors, handles container business logic and operates changes on it,
* complex Android interactions or communication with other features.
*/
interface QuickSettingsController {
/**
* Handles turning on/off tracking protection.
*
* */
fun handleTrackingProtectionToggled(trackingEnabled: Boolean)
/**
* Handles showing the tracking protection settings.
*/
fun handleTrackingProtectionSettingsSelected()
/**
* Handles reporting a webcompat issue for the indicated website.
*
* @param websiteUrl [String] the URL of the web page for which to report a site issue.
*/
fun handleReportTrackingProblem(websiteUrl: String)
/**
* Handles the case of the [TrackingProtectionView] needed to be displayed to the user.
*/
fun handleTrackingProtectionShown()
/**
* Handles the case of the [WebsitePermissionsView] needed to be displayed to the user.
*/
fun handlePermissionsShown()
/**
* Handles toggling a [WebsitePermission].
*
* @param permission [WebsitePermission] needing to be toggled.
*/
fun handlePermissionToggled(permission: WebsitePermission)
/**
* Handles a certain set of Android permissions being explicitly granted by the user.
*
* feature [PhoneFeature] which the user granted Android permission(s) for.
*/
fun handleAndroidPermissionGranted(feature: PhoneFeature)
}
/**
* Default behavior of [QuickSettingsController]. Other implementations are possible.
*
* @param context [Context] used for various Android interactions.
* @param quickSettingsStore [QuickSettingsFragmentStore] holding the [State] for all Views displayed
* in this Controller's Fragment.
* @param coroutineScope [CoroutineScope] used for structed concurrency.
* @param navController NavController] used for navigation.
* @param session [Session]? current browser state.
* @param sitePermissions [SitePermissions]? list of website permissions and their status.
* @param settings [Settings] application settings.
* @param permissionStorage [PermissionStorage] app state for website permissions exception.
* @param reload [ReloadUrlUseCase] callback allowing for reloading the current web page.
* @param addNewTab [AddNewTabUseCase] callback allowing for loading a URL in a new tab.
* @param requestRuntimePermissions [OnNeedToRequestPermissions] callback allowing for requesting
* specific Android runtime permissions.
* @param reportSiteIssue callback allowing to report an issue with the current web page.
* @param displayTrackingProtection callback for when the [TrackingProtectionView] needs to be displayed.
* @param displayPermissions callback for when [WebsitePermissionsView] needs to be displayed.
* @param dismiss callback allowing to request this entire Fragment to be dismissed.
* @param trackingProtectionUseCases usecase allowing us to add or remove tracking protection exceptions
*/
@Suppress("TooManyFunctions")
class DefaultQuickSettingsController(
private val context: Context,
private val quickSettingsStore: QuickSettingsFragmentStore,
private val coroutineScope: CoroutineScope,
private val navController: NavController,
private val session: Session?,
private var sitePermissions: SitePermissions?,
private val settings: Settings,
private val permissionStorage: PermissionStorage,
private val reload: ReloadUrlUseCase,
private val addNewTab: AddNewTabUseCase,
private val requestRuntimePermissions: OnNeedToRequestPermissions = { },
private val reportSiteIssue: () -> Unit,
private val displayTrackingProtection: () -> Unit,
private val displayPermissions: () -> Unit,
private val dismiss: () -> Unit,
private val trackingProtectionUseCases: TrackingProtectionUseCases
) : QuickSettingsController {
override fun handleTrackingProtectionToggled(
trackingEnabled: Boolean
) {
session?.let {
if (trackingEnabled) {
trackingProtectionUseCases.removeException(it)
} else {
context.metrics.track(Event.TrackingProtectionException)
trackingProtectionUseCases.addException(it)
}
}
reload(session)
quickSettingsStore.dispatch(
TrackingProtectionAction.TrackingProtectionToggled(trackingEnabled)
)
}
override fun handleTrackingProtectionSettingsSelected() {
val directions =
QuickSettingsSheetDialogFragmentDirections
.actionQuickSettingsSheetDialogFragmentToTrackingProtectionFragment()
navController.navigate(directions)
}
@ExperimentalCoroutinesApi
@UseExperimental(ObsoleteCoroutinesApi::class)
override fun handleReportTrackingProblem(websiteUrl: String) {
val reportUrl = String.format(BrowserFragment.REPORT_SITE_ISSUE_URL, websiteUrl)
addNewTab(reportUrl)
if (session?.isCustomTabSession() == true) {
reportSiteIssue()
}
dismiss()
}
override fun handleTrackingProtectionShown() {
displayTrackingProtection()
}
override fun handlePermissionsShown() {
displayPermissions()
}
override fun handlePermissionToggled(permission: WebsitePermission) {
val featureToggled = permission.getBackingFeature()
when (permission.isBlockedByAndroid) {
true -> handleAndroidPermissionRequest(featureToggled.androidPermissionsList)
false -> {
sitePermissions = sitePermissions!!.toggle(featureToggled).also {
handlePermissionsChange(it)
}
quickSettingsStore.dispatch(
WebsitePermissionAction.TogglePermission(
permission,
featureToggled.getActionLabel(context, sitePermissions, settings),
featureToggled.shouldBeEnabled(context, sitePermissions, settings)
)
)
}
}
}
override fun handleAndroidPermissionGranted(feature: PhoneFeature) {
quickSettingsStore.dispatch(
WebsitePermissionAction.TogglePermission(
feature.getCorrespondingPermission(),
feature.getActionLabel(context, sitePermissions, settings),
feature.shouldBeEnabled(context, sitePermissions, settings)
)
)
}
/**
* Request a certain set of runtime Android permissions.
*
* User's approval should be received in the [handleAndroidPermissionGranted] method but this is not enforced.
*
* @param requestedPermissions [Array]<[String]> runtime permissions needed to be requested.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun handleAndroidPermissionRequest(requestedPermissions: Array<String>) {
requestRuntimePermissions(requestedPermissions)
}
/**
* Updates the list of [SitePermissions] for this current website and reloads it to allow / block
* new functionality in the web page.
*
* @param updatedPermissions [SitePermissions] updated website permissions.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun handlePermissionsChange(updatedPermissions: SitePermissions) {
coroutineScope.launch(Dispatchers.IO) {
permissionStorage.updateSitePermissions(updatedPermissions)
reload(session)
}
}
/**
* Each [WebsitePermission] is mapped after a [PhoneFeature].
*
* Get this [WebsitePermission]'s [PhoneFeature].
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun WebsitePermission.getBackingFeature(): PhoneFeature = when (this) {
is WebsitePermission.Camera -> PhoneFeature.CAMERA
is WebsitePermission.Microphone -> PhoneFeature.MICROPHONE
is WebsitePermission.Notification -> PhoneFeature.NOTIFICATION
is WebsitePermission.Location -> PhoneFeature.LOCATION
}
/**
* Get the specific [WebsitePermission] implementation which this [PhoneFeature] is tied to.
*
* **The result only informs about the type of [WebsitePermission].
* The resulting object's properties are just stubs and not dependable.**
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun PhoneFeature.getCorrespondingPermission(): WebsitePermission {
val defaultStatus = ""
val defaultEnabled = false
val defaultVisible = false
val defaultBlockedByAndroid = false
val defaultWebsitePermission: WebsitePermission? = null
return when (this) {
PhoneFeature.CAMERA -> WebsitePermission.Camera(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
PhoneFeature.LOCATION -> WebsitePermission.Location(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
PhoneFeature.MICROPHONE -> WebsitePermission.Microphone(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
PhoneFeature.NOTIFICATION -> WebsitePermission.Notification(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
PhoneFeature.AUTOPLAY -> defaultWebsitePermission!! // fail-fast
}
}
}