Bug 1825028 - Delay init of MozillaOnline build until privacy notice accepted (#1498)

Co-authored-by: Christian Sadilek <christian.sadilek@gmail.com>
Co-authored-by: Jonathan Almeida <jalmeida@mozilla.com>
fenix/112.0
mergify[bot] 1 year ago committed by GitHub
parent 0b370d2ae6
commit f4774f55dc

@ -33,6 +33,11 @@ android {
testBuildType project.property("testBuildType") testBuildType project.property("testBuildType")
} }
// This allows overriding the target activity for MozillaOnline builds, which happens
// as part of the defaultConfig below, and applies to all other configurations (Nightly,
// Beta, and Release.)
def targetActivity = "HomeActivity"
defaultConfig { defaultConfig {
applicationId "org.mozilla" applicationId "org.mozilla"
minSdkVersion Config.minSdkVersion minSdkVersion Config.minSdkVersion
@ -66,16 +71,18 @@ android {
buildConfigField "String", "AMO_SERVER_URL", "\"https://services.addons.mozilla.org\"" buildConfigField "String", "AMO_SERVER_URL", "\"https://services.addons.mozilla.org\""
def deepLinkSchemeValue = "fenix-dev" def deepLinkSchemeValue = "fenix-dev"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = [
"deepLinkScheme": deepLinkSchemeValue
]
// Build flag for "Mozilla Online" variants. See `Config.isMozillaOnline`. // Build flag for "Mozilla Online" variants. See `Config.isMozillaOnline`.
if (project.hasProperty("mozillaOnline") || gradle.hasProperty("localProperties.mozillaOnline")) { if (project.hasProperty("mozillaOnline") || gradle.hasProperty("localProperties.mozillaOnline")) {
buildConfigField "boolean", "MOZILLA_ONLINE", "true" buildConfigField "boolean", "MOZILLA_ONLINE", "true"
targetActivity = "MozillaOnlineHomeActivity"
} else { } else {
buildConfigField "boolean", "MOZILLA_ONLINE", "false" buildConfigField "boolean", "MOZILLA_ONLINE", "false"
} }
manifestPlaceholders = [
"targetActivity": targetActivity,
"deepLinkScheme": deepLinkSchemeValue
]
} }
def releaseTemplate = { def releaseTemplate = {
@ -112,7 +119,10 @@ android {
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
def deepLinkSchemeValue = "fenix-nightly" def deepLinkSchemeValue = "fenix-nightly"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = ["deepLinkScheme": deepLinkSchemeValue] manifestPlaceholders = [
"deepLinkScheme": deepLinkSchemeValue,
"targetActivity": targetActivity
]
} }
beta releaseTemplate >> { beta releaseTemplate >> {
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
@ -128,7 +138,8 @@ android {
// - https://issuetracker.google.com/issues/36924841 // - https://issuetracker.google.com/issues/36924841
// - https://issuetracker.google.com/issues/36905922 // - https://issuetracker.google.com/issues/36905922
"sharedUserId": "org.mozilla.firefox.sharedID", "sharedUserId": "org.mozilla.firefox.sharedID",
"deepLinkScheme": deepLinkSchemeValue "deepLinkScheme": deepLinkSchemeValue,
"targetActivity": targetActivity
] ]
} }
release releaseTemplate >> { release releaseTemplate >> {
@ -145,7 +156,8 @@ android {
// - https://issuetracker.google.com/issues/36924841 // - https://issuetracker.google.com/issues/36924841
// - https://issuetracker.google.com/issues/36905922 // - https://issuetracker.google.com/issues/36905922
"sharedUserId": "org.mozilla.firefox.sharedID", "sharedUserId": "org.mozilla.firefox.sharedID",
"deepLinkScheme": deepLinkSchemeValue "deepLinkScheme": deepLinkSchemeValue,
"targetActivity": targetActivity,
] ]
} }
} }

@ -33,7 +33,7 @@ import org.mozilla.fenix.helpers.HomeActivityTestRule
* *
* Say no to main thread IO! 🙅 * Say no to main thread IO! 🙅
*/ */
private const val EXPECTED_SUPPRESSION_COUNT = 18 private const val EXPECTED_SUPPRESSION_COUNT = 17
/** /**
* The number of times we call the `runBlocking` coroutine method on the main thread during this * The number of times we call the `runBlocking` coroutine method on the main thread during this

@ -58,7 +58,7 @@
<activity-alias <activity-alias
android:name="${applicationId}.App" android:name="${applicationId}.App"
android:exported="true" android:exported="true"
android:targetActivity=".HomeActivity"> android:targetActivity=".${targetActivity}">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -140,6 +140,9 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".MozillaOnlineHomeActivity"
android:exported="false"/>
<activity android:name=".home.mozonline.PrivacyContentDisplayActivity" <activity android:name=".home.mozonline.PrivacyContentDisplayActivity"
android:exported="false"/> android:exported="false"/>

@ -11,7 +11,6 @@ import android.os.Build.VERSION.SDK_INT
import android.os.StrictMode import android.os.StrictMode
import android.os.SystemClock import android.os.SystemClock
import android.util.Log.INFO import android.util.Log.INFO
import androidx.annotation.CallSuper
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
@ -87,6 +86,7 @@ import org.mozilla.fenix.ext.isNotificationChannelEnabled
import org.mozilla.fenix.ext.setCustomEndpointIfAvailable import org.mozilla.fenix.ext.setCustomEndpointIfAvailable
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.onboarding.FenixOnboarding
import org.mozilla.fenix.onboarding.MARKETING_CHANNEL_ID import org.mozilla.fenix.onboarding.MARKETING_CHANNEL_ID
import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks
import org.mozilla.fenix.perf.ProfilerMarkerFactProcessor import org.mozilla.fenix.perf.ProfilerMarkerFactProcessor
@ -124,11 +124,24 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
private set private set
override fun onCreate() { override fun onCreate() {
super.onCreate()
if (shouldShowPrivacyNotice()) {
// For Mozilla Online build: Delay initialization on first run until privacy notice
// is accepted by the user.
return
}
initialize()
}
/**
* Initializes Fenix and all required subsystems such as Nimbus, Glean and Gecko.
*/
fun initialize() {
// We measure ourselves to avoid a call into Glean before its loaded. // We measure ourselves to avoid a call into Glean before its loaded.
val start = SystemClock.elapsedRealtimeNanos() val start = SystemClock.elapsedRealtimeNanos()
super.onCreate()
setupInAllProcesses() setupInAllProcesses()
if (!isMainProcess()) { if (!isMainProcess()) {
@ -155,6 +168,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
} }
@OptIn(DelicateCoroutinesApi::class) // GlobalScope usage @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
@VisibleForTesting
protected open fun initializeGlean() { protected open fun initializeGlean() {
val telemetryEnabled = settings().isTelemetryEnabled val telemetryEnabled = settings().isTelemetryEnabled
@ -195,16 +209,16 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
) )
} }
@CallSuper @VisibleForTesting
open fun setupInAllProcesses() { protected open fun setupInAllProcesses() {
setupCrashReporting() setupCrashReporting()
// We want the log messages of all builds to go to Android logcat // We want the log messages of all builds to go to Android logcat
Log.addSink(FenixLogSink(logsDebug = Config.channel.isDebug)) Log.addSink(FenixLogSink(logsDebug = Config.channel.isDebug))
} }
@CallSuper @VisibleForTesting
open fun setupInMainProcessOnly() { protected open fun setupInMainProcessOnly() {
// ⚠️ DO NOT ADD ANYTHING ABOVE THIS LINE. // ⚠️ DO NOT ADD ANYTHING ABOVE THIS LINE.
// Especially references to the engine/BrowserStore which can alter the app initialization. // Especially references to the engine/BrowserStore which can alter the app initialization.
// See: https://github.com/mozilla-mobile/fenix/issues/26320 // See: https://github.com/mozilla-mobile/fenix/issues/26320
@ -933,4 +947,14 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
components.useCases.wallpaperUseCases.initialize() components.useCases.wallpaperUseCases.initialize()
} }
} }
/**
* Checks whether or not a privacy notice needs to be displayed before
* the application can continue to initialize.
*/
internal fun shouldShowPrivacyNotice(): Boolean {
return Config.channel.isMozillaOnline &&
settings().shouldShowPrivacyPopWindow &&
!FenixOnboarding(this).userHasBeenOnboarded()
}
} }

@ -0,0 +1,29 @@
/* 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
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.mozilla.fenix.home.mozonline.showPrivacyPopWindow
/**
* This activity is specific to the Mozilla Online build and used to display
* a privacy notice on first run. Once the privacy notice is accepted, and for
* all subsequent launches, it will simply launch the Fenix [HomeActivity].
*/
open class MozillaOnlineHomeActivity : AppCompatActivity() {
final override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if ((this.application as FenixApplication).shouldShowPrivacyNotice()) {
showPrivacyPopWindow(this.applicationContext, this)
} else {
startActivity(Intent(this, HomeActivity::class.java))
finish()
}
}
}

@ -78,7 +78,6 @@ import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.content.getColorFromAttr import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.android.content.res.resolveAttribute import mozilla.components.support.ktx.android.content.res.resolveAttribute
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.Config
import org.mozilla.fenix.GleanMetrics.Events import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.GleanMetrics.HomeScreen import org.mozilla.fenix.GleanMetrics.HomeScreen
import org.mozilla.fenix.GleanMetrics.PrivateBrowsingShortcutCfr import org.mozilla.fenix.GleanMetrics.PrivateBrowsingShortcutCfr
@ -106,7 +105,6 @@ import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.gleanplumb.DefaultMessageController import org.mozilla.fenix.gleanplumb.DefaultMessageController
import org.mozilla.fenix.gleanplumb.MessagingFeature import org.mozilla.fenix.gleanplumb.MessagingFeature
import org.mozilla.fenix.gleanplumb.NimbusMessagingController import org.mozilla.fenix.gleanplumb.NimbusMessagingController
import org.mozilla.fenix.home.mozonline.showPrivacyPopWindow
import org.mozilla.fenix.home.pocket.DefaultPocketStoriesController import org.mozilla.fenix.home.pocket.DefaultPocketStoriesController
import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory import org.mozilla.fenix.home.pocket.PocketRecommendedStoriesCategory
import org.mozilla.fenix.home.recentbookmarks.RecentBookmarksFeature import org.mozilla.fenix.home.recentbookmarks.RecentBookmarksFeature
@ -211,13 +209,6 @@ class HomeFragment : Fragment() {
bundleArgs = args.toBundle() bundleArgs = args.toBundle()
if (!onboarding.userHasBeenOnboarded() &&
requireContext().settings().shouldShowPrivacyPopWindow &&
Config.channel.isMozillaOnline
) {
showPrivacyPopWindow(requireContext(), requireActivity())
}
// DO NOT MOVE ANYTHING BELOW THIS addMarker CALL! // DO NOT MOVE ANYTHING BELOW THIS addMarker CALL!
requireComponents.core.engine.profiler?.addMarker( requireComponents.core.engine.profiler?.addMarker(
MarkersFragmentLifecycleCallbacks.MARKER_NAME, MarkersFragmentLifecycleCallbacks.MARKER_NAME,

@ -5,29 +5,20 @@
package org.mozilla.fenix.home.mozonline package org.mozilla.fenix.home.mozonline
import android.app.Activity import android.app.Activity
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.util.AttributeSet
import android.view.View import android.view.View
import android.webkit.WebView
import android.widget.ImageButton import android.widget.ImageButton
import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineView
import mozilla.components.feature.contextmenu.DefaultSelectionActionDelegate
import mozilla.components.feature.search.BrowserStoreSearchAdapter
import mozilla.components.support.ktx.android.content.call
import mozilla.components.support.ktx.android.content.email
import mozilla.components.support.ktx.android.content.share
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
/** /**
* A special activity for displaying the detail content about privacy hyperlinked in alert dialog. * A special activity for displaying the detail content about privacy hyperlinked in alert dialog.
*/ */
class PrivacyContentDisplayActivity : Activity(), EngineSession.Observer { class PrivacyContentDisplayActivity : Activity(), EngineSession.Observer {
private lateinit var engineView: EngineView private lateinit var webView: WebView
private lateinit var closeButton: ImageButton private lateinit var closeButton: ImageButton
private lateinit var engineSession: EngineSession
private var url: String? = "" private var url: String? = ""
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -37,48 +28,20 @@ class PrivacyContentDisplayActivity : Activity(), EngineSession.Observer {
url = addr.getString("url") url = addr.getString("url")
} }
engineView = findViewById<View>(R.id.privacyContentEngineView) as EngineView webView = findViewById<View>(R.id.privacyContentEngineView) as WebView
webView.settings.javaScriptEnabled = true
closeButton = findViewById<View>(R.id.privacyContentCloseButton) as ImageButton closeButton = findViewById<View>(R.id.privacyContentCloseButton) as ImageButton
engineSession = components.core.engine.createSession()
}
override fun onCreateView(
parent: View?,
name: String,
context: Context,
attrs: AttributeSet,
): View? = when (name) {
EngineView::class.java.name -> components.core.engine.createView(context, attrs).apply {
selectionActionDelegate = DefaultSelectionActionDelegate(
BrowserStoreSearchAdapter(
components.core.store,
),
resources = context.resources,
shareTextClicked = { share(it) },
emailTextClicked = { email(it) },
callTextClicked = { call(it) },
)
}.asView()
else -> super.onCreateView(parent, name, context, attrs)
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
engineSession.register(this)
engineSession.let { engineSession ->
engineView.render(engineSession)
url?.let { engineSession.loadUrl(it) }
}
closeButton.setOnClickListener { finish() }
}
override fun onStop() { url?.let {
super.onStop() webView.loadUrl(it)
engineSession.unregister(this) }
}
override fun onDestroy() { closeButton.setOnClickListener {
super.onDestroy() finish()
engineSession.close() }
} }
} }

@ -6,13 +6,16 @@ package org.mozilla.fenix.home.mozonline
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.text.SpannableString import android.text.SpannableString
import android.text.Spanned import android.text.Spanned
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.MetricServiceType import org.mozilla.fenix.components.metrics.MetricServiceType
import org.mozilla.fenix.ext.application
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -57,6 +60,10 @@ fun showPrivacyPopWindow(context: Context, activity: Activity) {
context.settings().shouldShowPrivacyPopWindow = false context.settings().shouldShowPrivacyPopWindow = false
context.settings().isMarketingTelemetryEnabled = true context.settings().isMarketingTelemetryEnabled = true
context.components.analytics.metrics.start(MetricServiceType.Marketing) context.components.analytics.metrics.start(MetricServiceType.Marketing)
// Now that the privacy notice is accepted, application initialization can continue.
context.application.initialize()
activity.startActivity(Intent(activity, HomeActivity::class.java))
activity.finish()
} }
.setNeutralButton( .setNeutralButton(
context.getString(R.string.privacy_notice_neutral_button_2), context.getString(R.string.privacy_notice_neutral_button_2),

@ -33,7 +33,7 @@ private val mainLooper = Looper.getMainLooper()
* Manages strict mode settings for the application. * Manages strict mode settings for the application.
*/ */
open class StrictModeManager( open class StrictModeManager(
config: Config, private val config: Config,
// Ideally, we'd pass in a more specific value but there is a circular dependency: StrictMode // Ideally, we'd pass in a more specific value but there is a circular dependency: StrictMode
// is passed into Core but we'd need to pass in Core here. Instead, we take components and later // is passed into Core but we'd need to pass in Core here. Instead, we take components and later
@ -112,6 +112,14 @@ open class StrictModeManager(
*/ */
open fun <R> resetAfter(policy: StrictMode.ThreadPolicy, functionBlock: () -> R): R { open fun <R> resetAfter(policy: StrictMode.ThreadPolicy, functionBlock: () -> R): R {
fun instrumentedFunctionBlock(): R { fun instrumentedFunctionBlock(): R {
// For Mozilla Online builds, we decided not to add a profile marker because we need to
// prevent early initialization of the engine. These markers also have no distinct
// meaning to the Mozilla Online build variant.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1825028
if (config.channel.isMozillaOnline) {
return functionBlock()
}
val startProfilerTime = components.core.engine.profiler?.getProfilerTime() val startProfilerTime = components.core.engine.profiler?.getProfilerTime()
val returnValue = functionBlock() val returnValue = functionBlock()

@ -35,7 +35,7 @@
</LinearLayout> </LinearLayout>
<mozilla.components.concept.engine.EngineView <WebView
tools:ignore="Instantiatable" tools:ignore="Instantiatable"
android:id="@+id/privacyContentEngineView" android:id="@+id/privacyContentEngineView"
android:layout_width="match_parent" android:layout_width="match_parent"

Loading…
Cancel
Save