You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iceraven-browser/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFra...

229 lines
8.9 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.Intent
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.plus
import mozilla.components.browser.state.selector.findTabOrCustomTab
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.lib.state.ext.consumeFlow
import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.R
import org.mozilla.fenix.android.FenixDialogFragment
import org.mozilla.fenix.databinding.FragmentQuickSettingsDialogSheetBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.quicksettings.protections.ProtectionsView
/**
* Dialog that presents the user with information about
* - the current website and whether the connection is secured or not.
* - website tracking protection.
* - website permission.
*/
@Suppress("TooManyFunctions")
class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
private lateinit var quickSettingsStore: QuickSettingsFragmentStore
private lateinit var quickSettingsController: QuickSettingsController
private lateinit var websiteInfoView: WebsiteInfoView
private lateinit var websitePermissionsView: WebsitePermissionsView
private lateinit var clearSiteDataView: ClearSiteDataView
@VisibleForTesting
internal lateinit var protectionsView: ProtectionsView
private lateinit var interactor: QuickSettingsInteractor
private var tryToRequestPermissions: Boolean = false
private val args by navArgs<QuickSettingsSheetDialogFragmentArgs>()
private var _binding: FragmentQuickSettingsDialogSheetBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override val gravity: Int get() = args.gravity
override val layoutId: Int = R.layout.fragment_quick_settings_dialog_sheet
@Suppress("DEPRECATION")
// https://github.com/mozilla-mobile/fenix/issues/19920
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
val context = requireContext()
val components = context.components
val rootView = inflateRootView(container)
_binding = FragmentQuickSettingsDialogSheetBinding.bind(rootView)
val navController = findNavController()
quickSettingsStore = QuickSettingsFragmentStore.createStore(
context = context,
websiteUrl = args.url,
websiteTitle = args.title,
isSecured = args.isSecured,
permissions = args.sitePermissions,
settings = components.settings,
certificateName = args.certificateName,
permissionHighlights = args.permissionHighlights,
sessionId = args.sessionId,
isTrackingProtectionEnabled = args.isTrackingProtectionEnabled,
isCookieHandlingEnabled = args.isCookieHandlingEnabled,
)
quickSettingsController = DefaultQuickSettingsController(
context = context,
quickSettingsStore = quickSettingsStore,
browserStore = components.core.store,
ioScope = viewLifecycleOwner.lifecycleScope + Dispatchers.IO,
navController = navController,
sessionId = args.sessionId,
sitePermissions = args.sitePermissions,
settings = components.settings,
permissionStorage = components.core.permissionStorage,
reload = components.useCases.sessionUseCases.reload,
requestRuntimePermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS)
tryToRequestPermissions = true
},
displayPermissions = ::showPermissionsView,
)
interactor = QuickSettingsInteractor(quickSettingsController)
websiteInfoView = WebsiteInfoView(binding.websiteInfoLayout, interactor = interactor)
websitePermissionsView =
WebsitePermissionsView(binding.websitePermissionsLayout, interactor)
protectionsView =
ProtectionsView(binding.trackingProtectionLayout, interactor, context.settings())
clearSiteDataView = ClearSiteDataView(
context = context,
ioScope = viewLifecycleOwner.lifecycleScope + Dispatchers.IO,
containerView = binding.clearSiteDataLayout,
containerDivider = binding.clearSiteDataDivider,
interactor = interactor,
navController = navController,
)
return rootView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
observeTrackersChange(requireComponents.core.store)
consumeFrom(quickSettingsStore) {
websiteInfoView.update(it.webInfoState)
websitePermissionsView.update(it.websitePermissionsState)
protectionsView.update(it.protectionsState)
clearSiteDataView.update(it.webInfoState)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray,
) {
if (arePermissionsGranted(requestCode, grantResults)) {
PhoneFeature.findFeatureBy(permissions)?.let {
quickSettingsController.handleAndroidPermissionGranted(it)
}
} else {
val shouldShowRequestPermissionRationale =
permissions.all { shouldShowRequestPermissionRationale(it) }
if (!shouldShowRequestPermissionRationale && tryToRequestPermissions) {
// The user has permanently blocked these permissions and he/she is trying to enabling them.
// at this point, we are not able to request these permissions, the only way to allow
// them, it is to take the user to the system app setting page, and there the user
// can allow the permissions.
openSystemSettings()
}
}
tryToRequestPermissions = false
}
private fun arePermissionsGranted(requestCode: Int, grantResults: IntArray) =
requestCode == REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS && grantResults.all { it == PERMISSION_GRANTED }
private fun showPermissionsView() {
binding.websitePermissionsGroup.isVisible = true
}
private fun openSystemSettings() {
startActivity(
Intent().apply {
action = android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
},
)
}
@VisibleForTesting
internal fun provideTabId(): String = args.sessionId
@VisibleForTesting
internal fun observeTrackersChange(store: BrowserStore) {
consumeFlow(store) { flow ->
flow.mapNotNull { state ->
state.findTabOrCustomTab(provideTabId())
}.ifAnyChanged { tab ->
arrayOf(
tab.trackingProtection.blockedTrackers,
tab.trackingProtection.loadedTrackers,
)
}.collect {
updateTrackers(it)
}
}
}
@VisibleForTesting
internal fun updateTrackers(tab: SessionState) {
provideTrackingProtectionUseCases().fetchTrackingLogs(
tab.id,
onSuccess = { trackers ->
protectionsView.updateDetailsSection(trackers.isNotEmpty())
},
onError = {
Logger.error("QuickSettingsSheetDialogFragment - fetchTrackingLogs onError", it)
},
)
}
@VisibleForTesting
internal fun provideTrackingProtectionUseCases() = requireComponents.useCases.trackingProtectionUseCases
private companion object {
const val REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS = 4
}
}