/* 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.pm.PackageManager.PERMISSION_GRANTED import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout import androidx.preference.PreferenceManager import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch import mozilla.components.feature.sitepermissions.SitePermissions import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.browser.BrowserFragment 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 kotlin.coroutines.CoroutineContext import androidx.appcompat.view.ContextThemeWrapper import org.mozilla.fenix.R private const val KEY_URL = "KEY_URL" private const val KEY_IS_SECURED = "KEY_IS_SECURED" private const val KEY_SITE_PERMISSIONS = "KEY_SITE_PERMISSIONS" private const val KEY_IS_TP_ON = "KEY_IS_TP_ON" private const val REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS = 4 @SuppressWarnings("TooManyFunctions") class QuickSettingsSheetDialogFragment : BottomSheetDialogFragment(), CoroutineScope { private val safeArguments get() = requireNotNull(arguments) private val url: String by lazy { safeArguments.getString(KEY_URL) } private val isSecured: Boolean by lazy { safeArguments.getBoolean(KEY_IS_SECURED) } private val isTrackingProtectionOn: Boolean by lazy { safeArguments.getBoolean(KEY_IS_TP_ON) } private lateinit var quickSettingsComponent: QuickSettingsComponent private lateinit var job: Job var sitePermissions: SitePermissions? get() = safeArguments.getParcelable(KEY_SITE_PERMISSIONS) set(value) { safeArguments.putParcelable(KEY_SITE_PERMISSIONS, value) } override val coroutineContext: CoroutineContext get() = Dispatchers.IO + job override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) job = Job() } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val contextThemeWrapper = ContextThemeWrapper( activity, (activity as HomeActivity).themeManager.currentThemeResource ) val localInflater = inflater.cloneInContext(contextThemeWrapper) return localInflater.inflate( R.layout.fragment_quick_settings_dialog_sheet, container, false ) } override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) { super.onViewCreated(rootView, savedInstanceState) quickSettingsComponent = QuickSettingsComponent( rootView as ConstraintLayout, ActionBusFactory.get(this), QuickSettingsState( QuickSettingsState.Mode.Normal( url, isSecured, isTrackingProtectionOn, sitePermissions ) ) ) } companion object { const val FRAGMENT_TAG = "QUICK_SETTINGS_FRAGMENT_TAG" fun newInstance( url: String, isSecured: Boolean, isTrackingProtectionOn: Boolean, sitePermissions: SitePermissions? ): QuickSettingsSheetDialogFragment { val fragment = QuickSettingsSheetDialogFragment() val arguments = fragment.arguments ?: Bundle() with(arguments) { putString(KEY_URL, url) putBoolean(KEY_IS_SECURED, isSecured) putBoolean(KEY_IS_TP_ON, isTrackingProtectionOn) putParcelable(KEY_SITE_PERMISSIONS, sitePermissions) } fragment.arguments = arguments return fragment } } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, grantResults: IntArray ) { if (arePermissionsGranted(requestCode, grantResults)) { val feature = requireNotNull(PhoneFeature.findFeatureBy(permissions)) getManagedEmitter() .onNext(QuickSettingsChange.PermissionGranted(feature, sitePermissions)) } } override fun onDestroy() { super.onDestroy() job.cancel() } private fun arePermissionsGranted(requestCode: Int, grantResults: IntArray) = requestCode == REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS && grantResults.all { it == PERMISSION_GRANTED } override fun onResume() { super.onResume() getAutoDisposeObservable() .subscribe { when (it) { is QuickSettingsAction.SelectBlockedByAndroid -> { requestPermissions(it.permissions, REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS) } is QuickSettingsAction.SelectReportProblem -> { launch(Dispatchers.Main) { val reportUrl = String.format(BrowserFragment.REPORT_SITE_ISSUE_URL, it.url) requireComponents.useCases.sessionUseCases.loadUrl.invoke(reportUrl) } } is QuickSettingsAction.ToggleTrackingProtection -> { val trackingEnabled = it.trackingProtection with(requireComponents.core) { val policy = createTrackingProtectionPolicy(trackingEnabled) PreferenceManager.getDefaultSharedPreferences(context).edit() .putBoolean( context!!.getString(R.string.pref_key_tracking_protection), trackingEnabled ).apply() engine.settings.trackingProtectionPolicy = policy with(sessionManager) { sessions.forEach { getEngineSession(it)?.enableTrackingProtection( policy ) } } } launch(Dispatchers.Main) { getManagedEmitter().onNext( QuickSettingsChange.Change( url, isSecured, trackingEnabled, sitePermissions ) ) requireContext().components.useCases.sessionUseCases.reload.invoke() } } is QuickSettingsAction.TogglePermission -> { launch { sitePermissions = quickSettingsComponent.toggleSitePermission( context = requireContext(), featurePhone = it.featurePhone, url = url, sitePermissions = sitePermissions ) launch(Dispatchers.Main) { getManagedEmitter() .onNext( QuickSettingsChange.Stored( it.featurePhone, sitePermissions ) ) requireContext().components.useCases.sessionUseCases.reload.invoke() } } } } } if (isVisible) { getManagedEmitter() .onNext(QuickSettingsChange.PromptRestarted(sitePermissions)) } } }