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.
229 lines
8.9 KiB
Kotlin
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
|
|
}
|
|
}
|