Merge tag 'v84.1.0' into beta-sync
commit
12bbfa9c4c
@ -1,67 +1,76 @@
|
||||
## Overview ##
|
||||
|
||||
| Monday | Tuesday | Wednesday | Thursday | Friday |
|
||||
|-----------------|---------------------------|------------------------------|----------------|-------------|
|
||||
| (Sprint 1 Start)| | | | |
|
||||
| | Hard code freeze for Beta | | | Code Freeze/Planning
|
||||
| Sprint 2 Start / Release to Beta / Release Production in Play Store 1% | QA Beta / Promote Release 25% | Promote Release 100% | | |
|
||||
Firefox for Android roughly follows the [Firefox Gecko release schedule](https://wiki.mozilla.org/Release_Management/Calendar#Calendars).
|
||||
This means we cut a beta at the end of every two sprints, with a full cycle (~4 weeks) of baking on Beta before going to release. Uplifts must be approved by Release Owner (st3fan).
|
||||
|
||||
The [Firefox for Android release schedule](https://docs.google.com/spreadsheets/d/1HotjliSCGOp2nTkfXrxv8qYcurNpkqLWBKbbId6ovTY/edit#gid=0) contains more details related to specific Mobile handoffs.
|
||||
|
||||
| Monday | Tuesday | Wednesday | Thursday | Friday |
|
||||
|-----------------|---------------------------|--------------------------------|-------------------|-----------------|
|
||||
| (week 1) | | (Y.2 sprint ended) |Sprint **X.1** starts | |
|
||||
| (week 2) | | | | |
|
||||
| (week 3) | | Cut **X.1-beta** 12PST | **X.1-beta** QA / Sprint X.2 starts ||
|
||||
| (week 4) | | | | |
|
||||
| (week 5) | | Uplift L10N to **X.1-beta** | Sprint Z.1 starts | |
|
||||
| (week 6) | Build X.1-RC with GV Prod for QA | | | |
|
||||
| (week 7) | Release X.1 - 5% | Release X.1 20% / Cut Z.1-beta | Release X.1 100% | |
|
||||
|
||||
### Requirements
|
||||
- Jira account
|
||||
- JIRA access
|
||||
- Bugzilla account
|
||||
- Google Play access (for reviewing crashes)
|
||||
- Sentry access
|
||||
|
||||
## Release Checklist
|
||||
There are two releases this covers: the current sprint that is going out to Beta, and the previous beta that is going to Production.
|
||||
We will refer to the beta release going out as the *current* sprint release.
|
||||
There are two releases this covers: the current sprint that is going out to Beta, and the previous Beta that is going to Production.
|
||||
|
||||
## Start of sprint [Monday, 1st week of sprint]
|
||||
- [ ] Create milestone for *upcoming* sprint release. (e.g. if you are doing releng for v2.2, create v2.3 milestone)
|
||||
- [ ] If the upcoming release is a *major* (x.0) release, create an issue in the *upcoming* milestone: "What's New Entry for [*upcoming* release]" to track work for the SUMO page and Google Play release notes, e.g., if the current release is 2.3 but the upcoming one will be 3.0, make a "What's New" issue for 3.0. Product will use this to remember to check in with SUMO.
|
||||
- [ ] [Create an issue](https://github.com/mozilla-mobile/fenix/issues/new?template=release_checklist.md&title=Releng+for+) in the *upcoming* milestone: "Releng for v[release]". Find an engineer who will handle the next releng task and assign them.
|
||||
## Start of Sprint X.1 [Thursday, 1st week of sprint]
|
||||
- [ ] [Create an issue](https://github.com/mozilla-mobile/fenix/issues/new?template=release_checklist.md&title=Releng+for+) "Releng for v[release]" to track the current sprint.
|
||||
|
||||
## Release Day [Monday, 3rd week] Beta & Production
|
||||
- [ ] Promote previous Beta to Release
|
||||
- [ ] Cherry-pick all merged [automated L10N string PRs](https://github.com/mozilla-mobile/fenix/pull/6156) to add newly translated strings and open a PR against branch that is going to release. (TODO is this safe?) This will require review.
|
||||
- [ ] Tag the latest released RC version additionally with the tag of the release (v1.0-RC2 -> v1.0) (This can be done as soon as there are no more release blockers, does not need to be on Release Day.)
|
||||
- [ ] **Verify that the commit hash of the new release matches the most recent RC.** This ensures that the correct version will be released
|
||||
- [ ] Create a GitHub release build `vX.X.X` (v2.3.0) with the previous Beta branch as the target.
|
||||
- [ ] Smoketest the signed build
|
||||
- [ ] Load a url
|
||||
- [ ] Set up sync
|
||||
- [ ] Delete browsing data
|
||||
- [ ] Upload the signed APK from the Taskcluster `signing-production` task to the [release page](https://github.com/mozilla-mobile/fenix/releases)
|
||||
- [ ] Create a release request in Bugzilla to release to 1%. You can clone [this issue](https://bugzilla.mozilla.org/show_bug.cgi?id=1571967) and `need-info` someone from release management.
|
||||
## Sprint X.1 End [Wednesday, 2nd week] Cutting a Beta
|
||||
- [ ] Make a new Beta
|
||||
- [ ] Create a branch off of master (DO NOT PUSH YET) for the *current* milestone of format `releases/v2.3` (where 2.3 is the *current* milestone). After that, anything landing in master will be part of the next milestone.
|
||||
- [ ] On the new Beta branch, pin the AC version to the stable version ([example](https://github.com/mozilla-mobile/fenix/commit/e413da29f6a7a7d4a765817a9cd5687abbf27619)) with commit message "Issue #`<this releng issue>`: Pin to stable AC `<version>` for release v2.3" (replacing 2.3 with the version)
|
||||
- For each issue closed since the last release (run `kotlinc -script automation/releasetools/PrintMentionedIssuesAndPrs.kts` to get a list [see script for details] and paste it into the Releng issue):
|
||||
- [ ] Ensure it has the correct milestone.
|
||||
- [ ] Add `eng:qa:needed` flags on each issue that still needs it.
|
||||
- [ ] Go through the list of issues closed during this sprint in the Done column of the [Sprint Kanban](https://github.com/mozilla-mobile/fenix/projects/9) and make sure they all have the correct milestone.
|
||||
- [ ] Create a branch off of master (DO NOT PUSH YET) for the *current* milestone of format `releases/v85.0.0`. After that, anything landing in master will be part of the next release.
|
||||
- [ ] On the new Beta branch, pin the AC version to the stable version ([example](https://github.com/mozilla-mobile/fenix/commit/e413da29f6a7a7d4a765817a9cd5687abbf27619)) with commit message "Issue #`<this releng issue>`: Pin to stable AC `<version>` for release v85"
|
||||
- [ ] Update the title to include this AC version "Releng for v[release] with AC [version]"
|
||||
- Note: You will need code review to make changes to the release branch after this point, because it is a protected branch.
|
||||
- [ ] Push the branch.
|
||||
|
||||
- [ ] Create a GitHub pre-release build `vX.X.X-beta.1` (v2.3.0-beta.1) with the release branch as the target. This will kick off a build of the branch. You can see it in the mouseover of the CI badge of the branch in the commits view. Builds are found under `signing-*` task.
|
||||
- If you need to trigger a new RC build, you will need to draft and publish a new (pre-release) release. Editing an existing release and creating a new tag will not trigger a new RC build.
|
||||
|
||||
- [ ] Create a new PI (product integrity) request in Jira. You can clone [this issue](https://jira.mozilla.com/browse/PI-219).
|
||||
|
||||
### SUMO Verification [After Beta release]
|
||||
- [ ] If the *current* release is a major (x.0) release, review the SUMO article contents of the whats new / other sumo pages and make sure they are accurate with what is in this release. If not, escalate to Product Owner.
|
||||
- [ ] Create a GitHub pre-release [Release](https://github.com/mozilla-mobile/fenix/releases) with:
|
||||
- [ ] Tag of the format `vX.X.X-beta.1` (v85.0.0-beta.1)
|
||||
- [ ] The Target branch is the release branch (releases/v85.0.0)
|
||||
- [ ] For the description of the release, look at the [Jira boards](https://jira.mozilla.com/secure/RapidBoard.jspa?rapidView=299&projectKey=FNX&view=reporting&chart=sprintRetrospective&sprint=883) for the X.1 and previous Y.2 sprints and list the major features that were added. This will help with the release notes later on.
|
||||
- [ ] Click "Publish release". This will kick off a build of the branch. You can see it in the mouseover of the CI badge of the branch in the commits view. Builds are found under `signing-*` task.
|
||||
- If you need to trigger a new RC build, you **MUST** draft and publish a new (pre-release) release (optionally deleting both the release and the tag). Editing an existing release and creating a new tag will **not** trigger a new build.
|
||||
- [ ] Send an email to QA at mozilla-mobile-qa@softvision.com with a link to the Taskcluster build (subdirectory of the [Fenix CI](https://firefox-ci-tc.services.mozilla.com/tasks/index/mobile.v2.fenix.beta))
|
||||
|
||||
### During Beta Product Integrity (Beta Release until PI green signoff)
|
||||
### Bugfix uplifts / Beta Product Integrity (Beta Release until PI green signoff)
|
||||
- [ ] If bugs are considered release blocker then find someone to fix them on master and the milestone branch (cherry-pick / uplift)
|
||||
- [ ] If needed tag a new RC version (e.g. v1.0-RC2) and follow the submission checklist again.
|
||||
- [ ] Add the uplift request to the appropriate row in the [Uplifts document](https://docs.google.com/spreadsheets/d/1qIvHpcQ3BqJtlzV5T4M1MhbWVxkNiG-ToeYnWEBW4-I/edit#gid=0).
|
||||
- [ ] If needed tag a new beta version (e.g. v1.0-beta.2) and follow the submission checklist again.
|
||||
- [ ] Once there is GREEN QA signoff, file a [release management bugzilla for rollout](https://bugzilla.mozilla.org/show_bug.cgi?id=1664366)
|
||||
- [ ] Check Sentry each day for issues on [Firefox Beta](https://sentry.prod.mozaws.net/operations/firefox-beta/) and if nothing concerning, bump release in the bugzilla (5%, 20%, 100%)
|
||||
|
||||
### During Production Release Rollout [Tuesday, Wednesday following Monday Release Day]
|
||||
- [ ] Check Sentry for new crashes. File issues and triage.
|
||||
- [ ] Ask Relman in the bug if they see potential blockers on Google Play, and if not, request that they bump the release each day (25% Tu, 100% Wed)
|
||||
### Uplifting L10N strings to Beta [Wednesday, 2 weeks after sprint end]
|
||||
- [ ] Find the issue ([example](https://github.com/mozilla-mobile/fenix/issues/16381)) filed by L10N / delphine saying string are ready for uplift (it takes 2 weeks for localizers to prepare localization).
|
||||
- [ ] If there are new locales that are ready to be added to Release, add them to [l10n-release.toml](https://github.com/mozilla-mobile/fenix/blob/master/l10n-release.toml)
|
||||
- [ ] Run the [L10N uplift script](https://github.com/mozilla-mobile/fenix/blob/master/l10n-uplift.py) against the releases/vX.1 branch (releases/v85.0.0). There will likely be conflicts, but if you are confused, they should match the strings in [main/Nightly](https://github.com/mozilla-mobile/fenix/tree/master/app/src/main/res)
|
||||
- [ ] Once all conflicts are resolved, tag a new Beta to be released.
|
||||
- [ ] Notify delphine in the L10N issue that the strings have been uplifted, and string quarantine can be lifted
|
||||
|
||||
### Production Release Candidate [Tuesday, 3 weeks after X.1 Beta was cut]
|
||||
- [ ] In android-components: Create a dot release with the GeckoView Production release candidate.
|
||||
- [ ] Open a PR against the release branch (releases/v85.0.0) with the AC version bump "Pin to stable AC `<version>` for release v85`. You will need code review.
|
||||
- [ ] Create a GitHub pre-release [Release](https://github.com/mozilla-mobile/fenix/releases) with:
|
||||
- [ ] Tag of the format `vX.X.X-rc.1` (v85.0.0-rc.1)
|
||||
- [ ] The Target branch is the release branch (releases/v85.0.0)
|
||||
- [ ] For the description, copy the beta description
|
||||
- [ ] Send an email to QA at mozilla-mobile-qa@softvision.com with a link to the Taskcluster build (subdirectory of the [Fenix CI](https://firefox-ci-tc.services.mozilla.com/tasks/index/mobile.v2.fenix.release))
|
||||
|
||||
Major releases often need to be synchronized with other marketing activities (e.g. blog postings).
|
||||
### Production Release [Release day, from [release calendar](https://docs.google.com/spreadsheets/d/1HotjliSCGOp2nTkfXrxv8qYcurNpkqLWBKbbId6ovTY/edit#gid=0)]
|
||||
- [ ] Create a GitHub [Release](https://github.com/mozilla-mobile/fenix/releases) with:
|
||||
- [ ] Tag of the format `vX.1.X` (v85.1.0) (increment the minor version for new cuts)
|
||||
- [ ] The Target branch is the release branch (releases/v85.0.0)
|
||||
- [ ] For the description, copy the beta description
|
||||
- [ ] file Bugzilla ticket for [release manament](https://bugzilla.mozilla.org/show_bug.cgi?id=1672212)
|
||||
|
||||
## Room for improvement
|
||||
- [ ] Automate assigning milestones to closed issues (based on date, etc) #6199
|
||||
- [ ] Automate assignig `eng:qa:needed` to issues #6199
|
||||
- [ ] Automate verification that the commit hash matches the most recent RC
|
||||
- [ ] Builds generated as part of `signing-production` task look like `public/build/arm64-v8a/geckoBeta/target.apk`. This means that the dev must download, then rename them by hand. Could RM update these to generate `public/build/arm64-v8a/geckoBeta/firefox-preview-v3.0.0-rc.1-arm64-v8a.apk`, or similar?
|
||||
- [ ] Check Sentry for new crashes. File issues and triage.
|
||||
- [ ] Each day, bump the release rollout if nothing concerning (5%, 20%, 100%)
|
||||
|
@ -0,0 +1,23 @@
|
||||
# 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/
|
||||
|
||||
name: "Update Android-Components"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '*/15 * * * *'
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: "Update Android-Components"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: "Update Android-Components"
|
||||
uses: mozilla-mobile/relbot@master
|
||||
if: github.repository == 'mozilla-mobile/fenix'
|
||||
with:
|
||||
project: fenix
|
||||
command: update-android-components
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
@ -0,0 +1,56 @@
|
||||
/* 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.helpers.idlingresource
|
||||
|
||||
import android.view.View
|
||||
import androidx.test.espresso.IdlingResource
|
||||
import androidx.test.espresso.IdlingResource.ResourceCallback
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
|
||||
|
||||
class BottomSheetBehaviorStateIdlingResource(behavior: BottomSheetBehavior<*>) :
|
||||
BottomSheetCallback(), IdlingResource {
|
||||
|
||||
private var isIdle: Boolean
|
||||
private var callback: ResourceCallback? = null
|
||||
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
val wasIdle = isIdle
|
||||
isIdle = isIdleState(newState)
|
||||
if (!wasIdle && isIdle && callback != null) {
|
||||
callback!!.onTransitionToIdle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSlide(bottomSheet: View, slideOffset: Float) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return BottomSheetBehaviorStateIdlingResource::class.java.simpleName
|
||||
}
|
||||
|
||||
override fun isIdleNow(): Boolean {
|
||||
return isIdle
|
||||
}
|
||||
|
||||
override fun registerIdleTransitionCallback(callback: ResourceCallback) {
|
||||
this.callback = callback
|
||||
}
|
||||
|
||||
private fun isIdleState(state: Int): Boolean {
|
||||
return state != BottomSheetBehavior.STATE_DRAGGING &&
|
||||
state != BottomSheetBehavior.STATE_SETTLING &&
|
||||
// When detecting STATE_HALF_EXPANDED we immediately transit to STATE_HIDDEN.
|
||||
// Consider this also an intermediary state so not idling.
|
||||
state != BottomSheetBehavior.STATE_HALF_EXPANDED
|
||||
}
|
||||
|
||||
init {
|
||||
behavior.addBottomSheetCallback(this)
|
||||
val state = behavior.state
|
||||
isIdle = isIdleState(state)
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/* 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.helpers.matchers
|
||||
|
||||
import android.view.View
|
||||
import androidx.test.espresso.matcher.BoundedMatcher
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import org.hamcrest.Description
|
||||
|
||||
class BottomSheetBehaviorStateMatcher(private val expectedState: Int) :
|
||||
BoundedMatcher<View, View>(View::class.java) {
|
||||
|
||||
override fun describeTo(description: Description?) {
|
||||
description?.appendText("BottomSheetBehavior in state: \"$expectedState\"")
|
||||
}
|
||||
|
||||
override fun matchesSafely(item: View): Boolean {
|
||||
val behavior = BottomSheetBehavior.from(item)
|
||||
return behavior.state == expectedState
|
||||
}
|
||||
}
|
||||
|
||||
class BottomSheetBehaviorHalfExpandedMaxRatioMatcher(private val maxHalfExpandedRatio: Float) :
|
||||
BoundedMatcher<View, View>(View::class.java) {
|
||||
|
||||
override fun describeTo(description: Description?) {
|
||||
description?.appendText(
|
||||
"BottomSheetBehavior with an at max halfExpandedRation: " +
|
||||
"$maxHalfExpandedRatio"
|
||||
)
|
||||
}
|
||||
|
||||
override fun matchesSafely(item: View): Boolean {
|
||||
val behavior = BottomSheetBehavior.from(item)
|
||||
return behavior.halfExpandedRatio <= maxHalfExpandedRatio
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
/* 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.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.children
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import kotlinx.android.synthetic.main.activity_home.*
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
||||
|
||||
// BEFORE INCREASING THESE VALUES, PLEASE CONSULT WITH THE PERF TEAM.
|
||||
private const val EXPECTED_SUPPRESSION_COUNT = 11
|
||||
private const val EXPECTED_RUNBLOCKING_COUNT = 2
|
||||
private const val EXPECTED_COMPONENT_INIT_COUNT = 42
|
||||
private const val EXPECTED_VIEW_HIERARCHY_DEPTH = 12
|
||||
private const val EXPECTED_RECYCLER_VIEW_CONSTRAINT_LAYOUT_CHILDREN = 4
|
||||
|
||||
private val failureMsgStrictMode = getErrorMessage(
|
||||
shortName = "StrictMode suppression",
|
||||
implications = "suppressing a StrictMode violation can introduce performance regressions?"
|
||||
)
|
||||
|
||||
private val failureMsgRunBlocking = getErrorMessage(
|
||||
shortName = "runBlockingIncrement",
|
||||
implications = "using runBlocking may block the main thread and have other negative performance implications?"
|
||||
)
|
||||
|
||||
private val failureMsgComponentInit = getErrorMessage(
|
||||
shortName = "Component init",
|
||||
implications = "initializing new components on start up may be an indication that we're doing more work than necessary on start up?"
|
||||
)
|
||||
|
||||
private val failureMsgViewHierarchyDepth = getErrorMessage(
|
||||
shortName = "view hierarchy depth",
|
||||
implications = "having a deep view hierarchy can slow down measure/layout performance?"
|
||||
) + "Please note that we're not sure if this is a useful metric to assert: with your feedback, " +
|
||||
"we'll find out over time if it is or is not."
|
||||
|
||||
private val failureMsgRecyclerViewConstraintLayoutChildren = getErrorMessage(
|
||||
shortName = "ConstraintLayout being a common direct descendant of a RecyclerView",
|
||||
implications = "ConstraintLayouts are slow to inflate and are primarily used to flatten deep " +
|
||||
"view hierarchies so can be under-performant as a common RecyclerView child?"
|
||||
) + "Please note that we're not sure if this is a useful metric to assert: with your feedback, " +
|
||||
"we'll find out over time if it is or is not."
|
||||
|
||||
/**
|
||||
* A performance test to limit the number of StrictMode suppressions and number of runBlocking used
|
||||
* on startup.
|
||||
*
|
||||
* This test was written by the perf team.
|
||||
*
|
||||
* StrictMode detects main thread IO, which is often indicative of a performance issue.
|
||||
* It's easy to suppress StrictMode so we wrote a test to ensure we have a discussion
|
||||
* if the StrictMode count changes.
|
||||
*
|
||||
* RunBlocking is mostly used to return values to a thread from a coroutine. However, if that
|
||||
* coroutine takes too long, it can lead that thread to block every other operations.
|
||||
*
|
||||
* The perf team is code owners for this package so they should be notified when the counts are modified.
|
||||
*/
|
||||
class StartupExcessiveResourceUseTest {
|
||||
@get:Rule
|
||||
val activityTestRule = HomeActivityTestRule(skipOnboarding = true)
|
||||
|
||||
private val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||
|
||||
@Test
|
||||
fun verifyRunBlockingAndStrictModeSuppresionCount() {
|
||||
uiDevice.waitForIdle() // wait for async UI to load.
|
||||
|
||||
// This might cause intermittents: at an arbitrary point after start up (such as the visual
|
||||
// completeness queue), we might run code on the main thread that suppresses StrictMode,
|
||||
// causing this number to fluctuate depending on device speed. We'll deal with it if it occurs.
|
||||
val actualSuppresionCount = activityTestRule.activity.components.strictMode.suppressionCount.get().toInt()
|
||||
val actualRunBlocking = RunBlockingCounter.count.get()
|
||||
val actualComponentInitCount = ComponentInitCount.count.get()
|
||||
|
||||
val rootView = activityTestRule.activity.rootContainer
|
||||
val actualViewHierarchyDepth = countAndLogViewHierarchyDepth(rootView, 1)
|
||||
val actualRecyclerViewConstraintLayoutChildren = countRecyclerViewConstraintLayoutChildren(rootView, null)
|
||||
|
||||
assertEquals(failureMsgStrictMode, EXPECTED_SUPPRESSION_COUNT, actualSuppresionCount)
|
||||
assertEquals(failureMsgRunBlocking, EXPECTED_RUNBLOCKING_COUNT, actualRunBlocking)
|
||||
assertEquals(failureMsgComponentInit, EXPECTED_COMPONENT_INIT_COUNT, actualComponentInitCount)
|
||||
assertEquals(failureMsgViewHierarchyDepth, EXPECTED_VIEW_HIERARCHY_DEPTH, actualViewHierarchyDepth)
|
||||
assertEquals(
|
||||
failureMsgRecyclerViewConstraintLayoutChildren,
|
||||
EXPECTED_RECYCLER_VIEW_CONSTRAINT_LAYOUT_CHILDREN,
|
||||
actualRecyclerViewConstraintLayoutChildren
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun countAndLogViewHierarchyDepth(view: View, level: Int): Int {
|
||||
// Log for debugging purposes: not sure if this is actually helpful.
|
||||
val indent = "| ".repeat(level - 1)
|
||||
Log.d("Startup...Test", "${indent}$view")
|
||||
|
||||
return if (view !is ViewGroup) {
|
||||
level
|
||||
} else {
|
||||
val maxDepth = view.children.map { countAndLogViewHierarchyDepth(it, level + 1) }.maxOrNull()
|
||||
maxDepth ?: level
|
||||
}
|
||||
}
|
||||
|
||||
private fun countRecyclerViewConstraintLayoutChildren(view: View, parent: View?): Int {
|
||||
val viewValue = if (parent is RecyclerView && view is ConstraintLayout) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
return if (view !is ViewGroup) {
|
||||
viewValue
|
||||
} else {
|
||||
viewValue + view.children.sumBy { countRecyclerViewConstraintLayoutChildren(it, view) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getErrorMessage(shortName: String, implications: String) = """$shortName count does not match expected count.
|
||||
|
||||
If this PR removed a $shortName call, great! Please decrease the count.
|
||||
|
||||
Did this PR add or call code that increases the $shortName count?
|
||||
Did you know that $implications
|
||||
Please do your best to implement a solution without adding $shortName calls.
|
||||
Please consult the perf team if you have questions or believe that having this call
|
||||
is the optimal solution.
|
||||
|
||||
"""
|
@ -1,58 +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.ui
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
||||
|
||||
// PLEASE CONSULT WITH PERF TEAM BEFORE CHANGING THIS VALUE.
|
||||
private const val EXPECTED_SUPPRESSION_COUNT = 11
|
||||
|
||||
private const val FAILURE_MSG = """StrictMode startup suppression count does not match expected count.
|
||||
|
||||
If this PR removed code that suppressed StrictMode, great! Please decrement the suppression count.
|
||||
|
||||
Did this PR add or call code that suppresses a StrictMode violation?
|
||||
Did you know that suppressing a StrictMode violation can introduce performance regressions?
|
||||
|
||||
If so, please do your best to implement a solution without suppressing StrictMode.
|
||||
Please consult the perf team if you have questions or believe suppressing StrictMode
|
||||
is the optimal solution.
|
||||
|
||||
"""
|
||||
|
||||
/**
|
||||
* A performance test to limit the number of StrictMode suppressions on startup.
|
||||
* This test was written by the perf team.
|
||||
*
|
||||
* StrictMode detects main thread IO, which is often indicative of a performance issue.
|
||||
* It's easy to suppress StrictMode so we wrote a test to ensure we have a discussion
|
||||
* if the StrictMode count changes. The perf team is code owners for this file so they
|
||||
* should be notified when the count is modified.
|
||||
*
|
||||
* IF YOU UPDATE THE TEST NAME, UPDATE CODE OWNERS.
|
||||
*/
|
||||
class StrictModeStartupSuppressionCountTest {
|
||||
@get:Rule
|
||||
val activityTestRule = HomeActivityTestRule(skipOnboarding = true)
|
||||
|
||||
private val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||
|
||||
@Test
|
||||
fun verifyStrictModeSuppressionCount() {
|
||||
uiDevice.waitForIdle() // wait for async UI to load.
|
||||
|
||||
// This might cause intermittents: at an arbitrary point after start up (such as the visual
|
||||
// completeness queue), we might run code on the main thread that suppresses StrictMode,
|
||||
// causing this number to fluctuate depending on device speed. We'll deal with it if it occurs.
|
||||
val actual = activityTestRule.activity.components.strictMode.suppressionCount.toInt()
|
||||
assertEquals(FAILURE_MSG, EXPECTED_SUPPRESSION_COUNT, actual)
|
||||
}
|
||||
}
|
@ -1,39 +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
|
||||
|
||||
import android.content.Context
|
||||
import mozilla.components.service.experiments.Experiments
|
||||
import org.mozilla.fenix.ext.settings
|
||||
|
||||
object ExperimentsManager {
|
||||
|
||||
fun optOutSearchWidgetExperiment(context: Context) {
|
||||
// Release user has opted out of search widget CFR experiment, reset them to not see it.
|
||||
context.settings().setSearchWidgetExperiment(false)
|
||||
}
|
||||
|
||||
fun initSearchWidgetExperiment(context: Context) {
|
||||
// When the `search-widget-discoverability` experiment is active,set the pref to either
|
||||
// show or hide the search widget CFR (given other criteria are met as well).
|
||||
// Note that this will not take effect the first time the application has launched,
|
||||
// since there won't be enough time for the experiments library to get a list of experiments.
|
||||
// It will take effect the second time the application is launched.
|
||||
Experiments.withExperiment("fenix-search-widget") { branchName ->
|
||||
when (branchName) {
|
||||
"control_no_cfr" -> {
|
||||
context.settings().setSearchWidgetExperiment(false)
|
||||
}
|
||||
"treatment_cfr" -> {
|
||||
context.settings().setSearchWidgetExperiment(true)
|
||||
}
|
||||
else -> {
|
||||
// No branch matches so we're defaulting to no CFR
|
||||
context.settings().setSearchWidgetExperiment(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,107 +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.cfr
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.marginTop
|
||||
import kotlinx.android.synthetic.main.search_widget_cfr.view.*
|
||||
import kotlinx.android.synthetic.main.tracking_protection_onboarding_popup.view.drop_down_triangle
|
||||
import kotlinx.android.synthetic.main.tracking_protection_onboarding_popup.view.pop_up_triangle
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.SearchWidgetCreator
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.components.metrics.MetricController
|
||||
import org.mozilla.fenix.components.toolbar.ToolbarPosition
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
/**
|
||||
* Displays a CFR above the HomeFragment toolbar that recommends usage / installation of the search widget.
|
||||
*/
|
||||
class SearchWidgetCFR(
|
||||
private val context: Context,
|
||||
private val settings: Settings,
|
||||
private val metrics: MetricController,
|
||||
private val getToolbar: () -> View
|
||||
) {
|
||||
|
||||
fun displayIfNecessary() {
|
||||
if (settings.isInSearchWidgetExperiment &&
|
||||
settings.shouldDisplaySearchWidgetCfr() &&
|
||||
!isShown
|
||||
) {
|
||||
isShown = true
|
||||
showSearchWidgetCFR()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("InflateParams")
|
||||
private fun showSearchWidgetCFR() {
|
||||
settings.lastCfrShownTimeInMillis = System.currentTimeMillis()
|
||||
settings.incrementSearchWidgetCFRDisplayed()
|
||||
|
||||
val searchWidgetCFRDialog = Dialog(context)
|
||||
val layout = LayoutInflater.from(context)
|
||||
.inflate(R.layout.search_widget_cfr, null)
|
||||
val toolbarPosition = settings.toolbarPosition
|
||||
|
||||
layout.drop_down_triangle.isVisible = toolbarPosition == ToolbarPosition.TOP
|
||||
layout.pop_up_triangle.isVisible = toolbarPosition == ToolbarPosition.BOTTOM
|
||||
|
||||
val toolbar = getToolbar()
|
||||
|
||||
val gravity = Gravity.CENTER_HORIZONTAL or toolbarPosition.androidGravity
|
||||
|
||||
layout.cfr_neg_button.setOnClickListener {
|
||||
metrics.track(Event.SearchWidgetCFRNotNowPressed)
|
||||
searchWidgetCFRDialog.dismiss()
|
||||
settings.manuallyDismissSearchWidgetCFR()
|
||||
}
|
||||
|
||||
layout.cfr_pos_button.setOnClickListener {
|
||||
metrics.track(Event.SearchWidgetCFRAddWidgetPressed)
|
||||
SearchWidgetCreator.createSearchWidget(context)
|
||||
searchWidgetCFRDialog.dismiss()
|
||||
settings.manuallyDismissSearchWidgetCFR()
|
||||
}
|
||||
|
||||
searchWidgetCFRDialog.apply {
|
||||
setContentView(layout)
|
||||
}
|
||||
|
||||
searchWidgetCFRDialog.window?.let {
|
||||
it.setGravity(gravity)
|
||||
val attr = it.attributes
|
||||
attr.y =
|
||||
(toolbar.y + toolbar.height - toolbar.marginTop - toolbar.paddingTop).toInt()
|
||||
it.attributes = attr
|
||||
it.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
}
|
||||
|
||||
searchWidgetCFRDialog.setOnCancelListener {
|
||||
isShown = false
|
||||
metrics.track(Event.SearchWidgetCFRCanceled)
|
||||
}
|
||||
|
||||
searchWidgetCFRDialog.setOnDismissListener {
|
||||
isShown = false
|
||||
settings.incrementSearchWidgetCFRDismissed()
|
||||
}
|
||||
|
||||
searchWidgetCFRDialog.show()
|
||||
metrics.track(Event.SearchWidgetCFRDisplayed)
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Used to ensure multiple dialogs are not shown on top of each other
|
||||
var isShown = false
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/* 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 java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* Increases an AtomicInteger safely.
|
||||
*/
|
||||
fun AtomicInteger.getAndIncrementNoOverflow() {
|
||||
var prev: Int
|
||||
var next: Int
|
||||
do {
|
||||
prev = this.get()
|
||||
next = if (prev == Integer.MAX_VALUE) prev else prev + 1
|
||||
} while (!this.compareAndSet(prev, next))
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue