Merge tag 'v87.0.0-rc.1' into fork
commit
af23310679
@ -0,0 +1,62 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 180
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 7
|
||||
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: []
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- pin
|
||||
- "feature request 🌟"
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: false
|
||||
|
||||
# Set to true to ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: false
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: wontfix
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
See: https://github.com/mozilla-mobile/fenix/issues/17373
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
# unmarkComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
# closeComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 30
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
only: issues
|
||||
|
||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||
# pulls:
|
||||
# daysUntilStale: 30
|
||||
# markComment: >
|
||||
# This pull request has been automatically marked as stale because it has not had
|
||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||
# for your contributions.
|
||||
|
||||
issues:
|
||||
exemptLabels:
|
||||
- pin
|
||||
- "feature request 🌟"
|
@ -0,0 +1,103 @@
|
||||
name: Android build PR
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
run-build:
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: "Clean & Assemble Debug"
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
wrapper-cache-enabled: true
|
||||
dependencies-cache-enabled: true
|
||||
configuration-cache-enabled: true
|
||||
arguments: clean app:assembleDebug
|
||||
|
||||
run-testDebugUnitTest:
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: "Test Debug Unit Tests"
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
wrapper-cache-enabled: true
|
||||
dependencies-cache-enabled: true
|
||||
configuration-cache-enabled: true
|
||||
arguments: testDebugUnitTest
|
||||
|
||||
run-detekt:
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: "Detekt"
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
wrapper-cache-enabled: true
|
||||
dependencies-cache-enabled: true
|
||||
configuration-cache-enabled: true
|
||||
arguments: detekt
|
||||
- name: Archive detekt results
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: detekt report
|
||||
path: build/reports/detekt.html
|
||||
|
||||
run-ktlint:
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: "Ktlint"
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
wrapper-cache-enabled: true
|
||||
dependencies-cache-enabled: true
|
||||
configuration-cache-enabled: true
|
||||
arguments: ktlint
|
||||
|
||||
run-lintDebug:
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: "Lint Debug"
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
wrapper-cache-enabled: true
|
||||
dependencies-cache-enabled: true
|
||||
configuration-cache-enabled: true
|
||||
arguments: lintDebug
|
||||
- name: Archive lint results
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lintDebug report
|
||||
path: app/build/reports/lint-results-debug.html
|
||||
|
@ -0,0 +1,42 @@
|
||||
/* 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.browser.infobanner
|
||||
|
||||
import android.content.Context
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
|
||||
/**
|
||||
* [InfoBanner] that will automatically scroll with the top [BrowserToolbar].
|
||||
* Only to be used with [BrowserToolbar]s placed at the top of the screen.
|
||||
*
|
||||
* @param shouldScrollWithTopToolbar whether to follow the Y translation of the top toolbar or not
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class DynamicInfoBanner(
|
||||
private val context: Context,
|
||||
container: ViewGroup,
|
||||
@VisibleForTesting
|
||||
internal val shouldScrollWithTopToolbar: Boolean = false,
|
||||
message: String,
|
||||
dismissText: String,
|
||||
actionText: String? = null,
|
||||
dismissByHiding: Boolean = false,
|
||||
dismissAction: (() -> Unit)? = null,
|
||||
actionToPerform: (() -> Unit)? = null
|
||||
) : InfoBanner(
|
||||
context, container, message, dismissText, actionText, dismissByHiding, dismissAction, actionToPerform
|
||||
) {
|
||||
override fun showBanner() {
|
||||
super.showBanner()
|
||||
|
||||
if (shouldScrollWithTopToolbar) {
|
||||
(bannerLayout.layoutParams as CoordinatorLayout.LayoutParams).behavior = DynamicInfoBannerBehavior(
|
||||
context, null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/* 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.browser.infobanner
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||
|
||||
/**
|
||||
* A [CoordinatorLayout.Behavior] implementation to be used when placing [InfoBanner]
|
||||
* below the BrowserToolbar with which is has to scroll.
|
||||
*
|
||||
* This Behavior will keep the Y translations of [InfoBanner] and the top [BrowserToolbar] in sync
|
||||
* so that the banner will be shown between:
|
||||
* - the top of the container, being translated over the initial toolbar height (toolbar fully collapsed)
|
||||
* - immediately below the toolbar (toolbar fully expanded).
|
||||
*/
|
||||
class DynamicInfoBannerBehavior(
|
||||
context: Context?,
|
||||
attrs: AttributeSet?
|
||||
) : CoordinatorLayout.Behavior<View>(context, attrs) {
|
||||
@VisibleForTesting
|
||||
internal var toolbarHeight: Int = 0
|
||||
|
||||
override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
|
||||
if (dependency::class == BrowserToolbar::class) {
|
||||
toolbarHeight = dependency.height
|
||||
setBannerYTranslation(child, dependency.translationY)
|
||||
return true
|
||||
}
|
||||
|
||||
return super.layoutDependsOn(parent, child, dependency)
|
||||
}
|
||||
|
||||
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
|
||||
setBannerYTranslation(child, dependency.translationY)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun setBannerYTranslation(banner: View, newYTranslation: Float) {
|
||||
banner.translationY = toolbarHeight + newYTranslation
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/* 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.components.metrics
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import mozilla.components.lib.dataprotect.SecurePrefsReliabilityExperiment
|
||||
import mozilla.components.service.nimbus.NimbusApi
|
||||
import org.mozilla.fenix.experiments.ExperimentBranch
|
||||
import org.mozilla.fenix.experiments.Experiments
|
||||
import org.mozilla.fenix.ext.withExperiment
|
||||
|
||||
/**
|
||||
* Allows starting a quick test of ACs SecureAbove22Preferences that will emit Facts
|
||||
* for the basic operations and allow us to log them for later evaluation of APIs stability.
|
||||
*/
|
||||
class SecurePrefsTelemetry(
|
||||
private val appContext: Context,
|
||||
private val experiments: NimbusApi
|
||||
) {
|
||||
suspend fun startTests() {
|
||||
// The Android Keystore is used to secure the shared prefs only on API 23+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// These tests should run only if the experiment is live
|
||||
experiments.withExperiment(Experiments.ANDROID_KEYSTORE) { experimentBranch ->
|
||||
// .. and this device is not in the control group.
|
||||
if (experimentBranch == ExperimentBranch.TREATMENT) {
|
||||
SecurePrefsReliabilityExperiment(appContext)()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/* 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.components.toolbar
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import mozilla.components.concept.engine.EngineView
|
||||
import mozilla.components.concept.engine.EngineView.InputResult.INPUT_RESULT_UNHANDLED
|
||||
import org.mozilla.fenix.ext.settings
|
||||
|
||||
/**
|
||||
* ScrollingViewBehavior that will setScrollFlags on BrowserToolbar based on EngineView touch handling
|
||||
*/
|
||||
@ExperimentalCoroutinesApi
|
||||
class SwipeRefreshScrollingViewBehavior(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
private val engineView: EngineView,
|
||||
private val browserToolbarView: BrowserToolbarView
|
||||
) : AppBarLayout.ScrollingViewBehavior(context, attrs) {
|
||||
override fun onStartNestedScroll(
|
||||
coordinatorLayout: CoordinatorLayout,
|
||||
child: View,
|
||||
directTargetChild: View,
|
||||
target: View,
|
||||
axes: Int,
|
||||
type: Int
|
||||
): Boolean {
|
||||
|
||||
if (!browserToolbarView.view.context.settings().shouldUseBottomToolbar) {
|
||||
val shouldDisable = engineView.getInputResult() == INPUT_RESULT_UNHANDLED
|
||||
browserToolbarView.setScrollFlags(shouldDisable)
|
||||
}
|
||||
|
||||
return super.onStartNestedScroll(
|
||||
coordinatorLayout,
|
||||
child,
|
||||
directTargetChild,
|
||||
target,
|
||||
axes,
|
||||
type
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/* 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.experiments
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.StrictMode
|
||||
import io.sentry.Sentry
|
||||
import mozilla.components.service.nimbus.NimbusApi
|
||||
import mozilla.components.service.nimbus.Nimbus
|
||||
import mozilla.components.service.nimbus.NimbusDisabled
|
||||
import mozilla.components.service.nimbus.NimbusServerSettings
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import org.mozilla.fenix.components.isSentryEnabled
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.settings
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
fun createNimbus(context: Context, url: String?): NimbusApi =
|
||||
try {
|
||||
// Eventually we'll want to use `NimbusDisabled` when we have no NIMBUS_ENDPOINT.
|
||||
// but we keep this here to not mix feature flags and how we configure Nimbus.
|
||||
val serverSettings = if (!url.isNullOrBlank()) {
|
||||
NimbusServerSettings(url = Uri.parse(url))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Global opt out state is stored in Nimbus, and shouldn't be toggled to `true`
|
||||
// from the app unless the user does so from a UI control.
|
||||
// However, the user may have opt-ed out of mako experiments already, so
|
||||
// we should respect that setting here.
|
||||
val enabled =
|
||||
context.components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
|
||||
context.settings().isExperimentationEnabled
|
||||
}
|
||||
|
||||
Nimbus(context, serverSettings).apply {
|
||||
// This performs the minimal amount of work required to load branch and enrolment data
|
||||
// into memory. If `getExperimentBranch` is called from another thread between here
|
||||
// and the next nimbus disk write (setting `globalUserParticipation` or
|
||||
// `applyPendingExperiments()`) then this has you covered.
|
||||
// This call does its work on the db thread.
|
||||
initialize()
|
||||
|
||||
if (!enabled) {
|
||||
// This opts out of nimbus experiments. It involves writing to disk, so does its
|
||||
// work on the db thread.
|
||||
globalUserParticipation = enabled
|
||||
}
|
||||
|
||||
// We may have downloaded experiments on a previous run, so let's start using them
|
||||
// now. We didn't do this earlier, so as to make getExperimentBranch and friends returns
|
||||
// the same thing throughout the session. This call does its work on the db thread.
|
||||
applyPendingExperiments()
|
||||
|
||||
// Now fetch the experiments from the server. These will be available for feature
|
||||
// configuration on the next run of the app. This call launches on the fetch thread.
|
||||
fetchExperiments()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// Something went wrong. We'd like not to, but stability of the app is more important than
|
||||
// failing fast here.
|
||||
if (isSentryEnabled()) {
|
||||
Sentry.capture(e)
|
||||
} else {
|
||||
Logger.error("Failed to initialize Nimbus", e)
|
||||
}
|
||||
NimbusDisabled()
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
/* 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.ext
|
||||
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.browser.state.state.MediaState
|
||||
|
||||
fun BrowserState.getMediaStateForSession(sessionId: String): MediaState.State {
|
||||
return if (media.aggregate.activeTabId == sessionId) {
|
||||
media.aggregate.state
|
||||
} else {
|
||||
MediaState.State.NONE
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/* 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.home.mozonline
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
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.ext.components
|
||||
|
||||
/**
|
||||
* A special activity for displaying the detail content about privacy hyperlinked in alert dialog.
|
||||
*/
|
||||
|
||||
class PrivacyContentDisplayActivity : Activity(), EngineSession.Observer {
|
||||
private lateinit var engineView: EngineView
|
||||
private lateinit var closeButton: ImageButton
|
||||
private lateinit var engineSession: EngineSession
|
||||
private var url: String? = ""
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_privacy_content_display)
|
||||
val addr = intent.extras
|
||||
if (addr != null) {
|
||||
url = addr.getString("url")
|
||||
}
|
||||
|
||||
engineView = findViewById<View>(R.id.privacyContentEngineView) as EngineView
|
||||
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() {
|
||||
super.onStart()
|
||||
engineSession.register(this)
|
||||
engineSession.let { engineSession ->
|
||||
engineView.render(engineSession)
|
||||
url?.let { engineSession.loadUrl(it) }
|
||||
}
|
||||
closeButton.setOnClickListener { finish() }
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
engineSession.unregister(this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
engineSession.close()
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/* 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.home.mozonline
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.text.SpannableString
|
||||
import android.text.Spanned
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
fun showPrivacyPopWindow(context: Context, activity: Activity) {
|
||||
val content = context.getString(R.string.privacy_notice_content)
|
||||
|
||||
// Use hyperlinks to display details about privacy
|
||||
val messageClickable1 = context.getString(R.string.privacy_notice_clickable1)
|
||||
val messageClickable2 = context.getString(R.string.privacy_notice_clickable2)
|
||||
val messageClickable3 = context.getString(R.string.privacy_notice_clickable3)
|
||||
val messageSpannable = SpannableString(content)
|
||||
|
||||
val clickableSpan1 = PrivacyContentSpan(Position.POS1, context)
|
||||
val clickableSpan2 = PrivacyContentSpan(Position.POS2, context)
|
||||
val clickableSpan3 = PrivacyContentSpan(Position.POS3, context)
|
||||
|
||||
messageSpannable.setSpan(clickableSpan1, content.indexOf(messageClickable1),
|
||||
content.indexOf(messageClickable1) + messageClickable1.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
|
||||
messageSpannable.setSpan(clickableSpan2, content.indexOf(messageClickable2),
|
||||
content.indexOf(messageClickable2) + messageClickable2.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
|
||||
messageSpannable.setSpan(clickableSpan3, content.indexOf(messageClickable3),
|
||||
content.indexOf(messageClickable3) + messageClickable3.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
|
||||
|
||||
// Users can only use fenix after they agree with the privacy notice
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
.setPositiveButton(context.getString(R.string.privacy_notice_positive_button),
|
||||
DialogInterface.OnClickListener { _, _ ->
|
||||
context.settings().shouldShowPrivacyPopWindow = false
|
||||
})
|
||||
.setNeutralButton(context.getString(R.string.privacy_notice_neutral_button),
|
||||
DialogInterface.OnClickListener { _, _ -> exitProcess(0) })
|
||||
.setTitle(context.getString(R.string.privacy_notice_title))
|
||||
.setMessage(messageSpannable)
|
||||
.setCancelable(false)
|
||||
val alertDialog: AlertDialog = builder.create()
|
||||
alertDialog.show()
|
||||
alertDialog.findViewById<TextView>(android.R.id.message)?.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/* 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.home.mozonline
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.style.ClickableSpan
|
||||
import android.view.View
|
||||
|
||||
object Position {
|
||||
const val POS1 = 1
|
||||
const val POS2 = 2
|
||||
const val POS3 = 3
|
||||
}
|
||||
|
||||
object ADDR {
|
||||
const val URL1 = "https://www.mozilla.org/en-US/MPL/"
|
||||
const val URL2 = "https://www.mozilla.org/en-US/foundation/trademarks/policy/"
|
||||
const val URL3 = "https://www.mozilla.org/zh-CN/privacy/firefox/"
|
||||
}
|
||||
|
||||
class PrivacyContentSpan(var pos: Int, var context: Context) :
|
||||
ClickableSpan() {
|
||||
override fun onClick(widget: View) {
|
||||
/**
|
||||
* To avoid users directly using fenix by clicking these urls before
|
||||
* they click positive button of privacy notice alert dialog, start
|
||||
* PrivacyContentDisplayActivity to display them.
|
||||
*/
|
||||
val engineViewIntent = Intent(context, PrivacyContentDisplayActivity::class.java)
|
||||
engineViewIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
val addr = Bundle()
|
||||
when (pos) {
|
||||
Position.POS1 -> addr.putString("url", ADDR.URL1)
|
||||
Position.POS2 -> addr.putString("url", ADDR.URL2)
|
||||
Position.POS3 -> addr.putString("url", ADDR.URL3)
|
||||
}
|
||||
engineViewIntent.putExtras(addr)
|
||||
context.startActivity(engineViewIntent)
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
/* 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.media
|
||||
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.feature.media.service.AbstractMediaService
|
||||
import org.mozilla.fenix.ext.components
|
||||
|
||||
/**
|
||||
* [AbstractMediaService] implementation for injecting [BrowserStore] singleton.
|
||||
*/
|
||||
class MediaService : AbstractMediaService() {
|
||||
override val store: BrowserStore by lazy { components.core.store }
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/* 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.perf
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.annotation.VisibleForTesting.PRIVATE
|
||||
import mozilla.components.concept.base.profiler.Profiler
|
||||
import mozilla.components.support.base.facts.Action
|
||||
import mozilla.components.support.base.facts.Fact
|
||||
import mozilla.components.support.base.facts.FactProcessor
|
||||
|
||||
/**
|
||||
* A fact processor that adds Gecko profiler markers for [Fact]s matching a specific format.
|
||||
* We look for the following format:
|
||||
* ```
|
||||
* Fact(
|
||||
* action = Action.IMPLEMENTATION_DETAIL
|
||||
* item = <marker name>
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* This allows us to add profiler markers from android-components code. Using the Fact API for this
|
||||
* purpose, rather than calling [Profiler.addMarker] directly inside components, has trade-offs. Its
|
||||
* downsides are that it is less explicit and tooling does not work as well on it. However, we felt
|
||||
* it was worthwhile because:
|
||||
*
|
||||
* 1. we don't know what profiler markers are useful so we want to be able to iterate quickly.
|
||||
* Adding dependencies on the Profiler and landing these changes across two repos hinders that
|
||||
* 2. we want to instrument the code as close to specific method calls as possible (e.g.
|
||||
* GeckoSession.loadUrl) but it's not always easy to do so (e.g. in the previous example, passing a
|
||||
* Profiler reference to GeckoEngineSession is difficult because GES is not a global dependency)
|
||||
* 3. we can only add Profiler markers from the main thread so adding markers will become more
|
||||
* difficult if we have to understand the threading needs of each Profiler call site
|
||||
*
|
||||
* An additional benefit with having this infrastructure is that it's easy to add Profiler markers
|
||||
* for local debugging.
|
||||
*
|
||||
* That being said, if we find a location where it would be valuable to have a long term Profiler
|
||||
* marker, we should consider instrumenting it via the [Profiler] API.
|
||||
*/
|
||||
class ProfilerMarkerFactProcessor @VisibleForTesting(otherwise = PRIVATE) constructor(
|
||||
// We use a provider to defer accessing the profiler until we need it, because the property is a
|
||||
// child of the engine property and we don't want to initialize it earlier than we intend to.
|
||||
private val profilerProvider: () -> Profiler?,
|
||||
private val mainHandler: Handler = Handler(Looper.getMainLooper()),
|
||||
private val getMyLooper: () -> Looper? = { Looper.myLooper() }
|
||||
) : FactProcessor {
|
||||
|
||||
override fun process(fact: Fact) {
|
||||
if (fact.action != Action.IMPLEMENTATION_DETAIL) {
|
||||
return
|
||||
}
|
||||
|
||||
val markerName = fact.item
|
||||
|
||||
// Java profiler markers can only be added from the main thread so, for now, we push all
|
||||
// markers to the the main thread (which also groups all the markers together,
|
||||
// making it easier to read).
|
||||
val profiler = profilerProvider()
|
||||
if (getMyLooper() == mainHandler.looper) {
|
||||
profiler?.addMarker(markerName)
|
||||
} else {
|
||||
// To reduce the performance burden, we could early return if the profiler isn't active.
|
||||
// However, this would change the performance characteristics from when the profiler is
|
||||
// active and when it's inactive so we always post instead.
|
||||
val now = profiler?.getProfilerTime()
|
||||
mainHandler.post {
|
||||
// We set now to both start and end time because we want a marker of without duration
|
||||
// and if end is omitted, the duration is created implicitly.
|
||||
profiler?.addMarker(markerName, now, now, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(profilerProvider: () -> Profiler?) = ProfilerMarkerFactProcessor(profilerProvider)
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/* 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.perf
|
||||
|
||||
import android.view.View
|
||||
import androidx.core.view.doOnPreDraw
|
||||
import mozilla.components.concept.base.profiler.Profiler
|
||||
|
||||
/**
|
||||
* A container for functions for when adding a profiler marker is less readable
|
||||
* (e.g. multiple lines, more advanced logic).
|
||||
*/
|
||||
object ProfilerMarkers {
|
||||
|
||||
fun homeActivityOnStart(rootContainer: View, profiler: Profiler?) {
|
||||
rootContainer.doOnPreDraw {
|
||||
profiler?.addMarker("onPreDraw", "expected first frame via HomeActivity.onStart")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/* 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.perf
|
||||
|
||||
import android.os.Build
|
||||
import android.os.StrictMode
|
||||
import android.os.strictmode.Violation
|
||||
import androidx.annotation.RequiresApi
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import org.mozilla.fenix.utils.ManufacturerCodes
|
||||
|
||||
private const val FCQN_EDM_STORAGE_PROVIDER_BASE = "com.android.server.enterprise.storage.EdmStorageProviderBase"
|
||||
|
||||
/**
|
||||
* A [StrictMode.OnThreadViolationListener] that recreates
|
||||
* [StrictMode.ThreadPolicy.Builder.penaltyDeath] but will ignore some violations. For example,
|
||||
* sometimes OEMs will add code that violates StrictMode so we can ignore them here instead of
|
||||
* cluttering up our code with resetAfter.
|
||||
*
|
||||
* This class can only be used with Android P+ so we'd have to implement workarounds if the
|
||||
* violations we want to ignore affect older devices.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
class ThreadPenaltyDeathWithIgnoresListener(
|
||||
private val logger: Logger = Performance.logger
|
||||
) : StrictMode.OnThreadViolationListener {
|
||||
|
||||
override fun onThreadViolation(violation: Violation?) {
|
||||
if (violation == null) return
|
||||
|
||||
// Unfortunately, this method gets called many (~5+) times with the same violation so we end
|
||||
// up logging/throwing redundantly.
|
||||
if (shouldViolationBeIgnored(violation)) {
|
||||
logger.debug("Ignoring StrictMode ThreadPolicy violation", violation)
|
||||
} else {
|
||||
penaltyDeath(violation)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("TooGenericExceptionThrown") // we throw what StrictMode's penaltyDeath throws.
|
||||
private fun penaltyDeath(violation: Violation) {
|
||||
throw RuntimeException("StrictMode ThreadPolicy violation", violation)
|
||||
}
|
||||
|
||||
private fun shouldViolationBeIgnored(violation: Violation): Boolean =
|
||||
isSamsungLgEdmStorageProviderStartupViolation(violation)
|
||||
|
||||
private fun isSamsungLgEdmStorageProviderStartupViolation(violation: Violation): Boolean {
|
||||
// Root issue: https://github.com/mozilla-mobile/fenix/issues/17920
|
||||
//
|
||||
// This fix may address the issues seen in this bug:
|
||||
// https://github.com/mozilla-mobile/fenix/issues/15430
|
||||
// So we might be able to back out the changes made there. However, I don't have a device to
|
||||
// test so I didn't bother.
|
||||
//
|
||||
// This issue occurs on the Galaxy S10e, Galaxy A50, Note 10, and LG G7 FIT but not the S7:
|
||||
// I'm guessing it's just a problem on recent Samsungs and LGs so it's okay being in this P+
|
||||
// listener.
|
||||
if (!ManufacturerCodes.isSamsung && !ManufacturerCodes.isLG) {
|
||||
return false
|
||||
}
|
||||
|
||||
// To ignore this warning, we can inspect the stack trace. There are no parts of the
|
||||
// violation stack trace that are clearly unique to this violation but
|
||||
// EdmStorageProviderBase doesn't appear in Android code search so we match against it.
|
||||
// This class may be used in other violations that we're capable of fixing but this
|
||||
// code may ignore them. I think it's okay - we keep this code simple and if it was a serious
|
||||
// issue, we'd catch it on other manufacturers.
|
||||
return violation.stackTrace.any { it.className == FCQN_EDM_STORAGE_PROVIDER_BASE }
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue