1
0
Fork 0

Copione merged onto master

master
blallo 2020-08-19 00:00:21 +02:00
commit 543bc849ad
24 changed files with 1020 additions and 205 deletions

View File

@ -330,7 +330,6 @@ dependencies {
implementation Deps.androidx_coordinatorlayout
implementation Deps.sentry
implementation Deps.osslicenses_library
implementation Deps.leanplum_core
implementation Deps.leanplum_fcm
@ -429,7 +428,6 @@ dependencies {
implementation Deps.androidx_lifecycle_viewmodel
implementation Deps.androidx_core
implementation Deps.androidx_core_ktx
implementation Deps.androidx_dynamic_animation
implementation Deps.androidx_transition
implementation Deps.androidx_work_ktx
implementation Deps.google_material

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@ import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.ui.robots.clickUrlbar
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -254,4 +255,69 @@ class SmokeTest {
}
}
}
@Test
fun verifySearchEngineCanBeChangedTemporarilyUsingShortcuts() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
}.openSearch {
verifyKeyboardVisibility()
clickSearchEngineButton()
verifySearchEngineList()
changeDefaultSearchEngine("Amazon.com")
verifySearchEngineIcon("Amazon.com")
}.goToSearchEngine {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabDrawer {
// Changing search engine to Bing
}.openHomeScreen {
}.openSearch {
clickSearchEngineButton()
mDevice.waitForIdle()
changeDefaultSearchEngine("Bing")
verifySearchEngineIcon("Bing")
}.goToSearchEngine {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabDrawer {
// Changing search engine to DuckDuckGo
}.openHomeScreen {
}.openSearch {
clickSearchEngineButton()
mDevice.waitForIdle()
changeDefaultSearchEngine("DuckDuckGo")
verifySearchEngineIcon("DuckDuckGo")
}.goToSearchEngine {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabDrawer {
// Changing search engine to Twitter
}.openHomeScreen {
}.openSearch {
clickSearchEngineButton()
mDevice.waitForIdle()
changeDefaultSearchEngine("Twitter")
verifySearchEngineIcon("Twitter")
}.goToSearchEngine {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabDrawer {
// Changing search engine to Wikipedia
}.openHomeScreen {
}.openSearch {
clickSearchEngineButton()
changeDefaultSearchEngine("Wikipedia")
verifySearchEngineIcon("Wikipedia")
}.goToSearchEngine {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabDrawer {
// Checking whether the next search will be with default or not
}.openHomeScreen {
}.openSearch {
}.goToSearchEngine {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openNavigationToolbar {
clickUrlbar {
verifyDefaultSearchEngine("Google")
}
}
}
}

View File

@ -12,12 +12,14 @@ import android.net.Uri
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.BundleMatchers
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed

View File

@ -239,6 +239,12 @@ fun navigationToolbar(interact: NavigationToolbarRobot.() -> Unit): NavigationTo
return NavigationToolbarRobot.Transition()
}
fun clickUrlbar(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
urlBar().click()
SearchRobot().interact()
return SearchRobot.Transition()
}
private fun assertSuggestionsAreEqualTo(suggestionSize: Int, searchTerm: String) {
mDevice.waitForIdle()
awesomeBar().perform(typeText(searchTerm))

View File

@ -6,6 +6,7 @@
package org.mozilla.fenix.ui.robots
import android.widget.ToggleButton
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
@ -16,7 +17,9 @@ import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
@ -28,8 +31,11 @@ import androidx.test.uiautomator.Until
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.startsWith
import org.hamcrest.Matchers
import org.junit.Assert.assertEquals
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
/**
@ -47,6 +53,24 @@ class SearchRobot {
fun verifySearchSettings() = assertSearchSettings()
fun verifySearchBarEmpty() = assertSearchBarEmpty()
fun verifyKeyboardVisibility() = assertKeyboardVisibility(isExpectedToBeVisible = true)
fun verifySearchEngineList() = assertSearchEngineList()
fun verifySearchEngineIcon(expectedText: String) {
onView(withContentDescription(expectedText))
}
fun verifyDefaultSearchEngine(expectedText: String) = assertDefaultSearchEngine(expectedText)
fun changeDefaultSearchEngine(searchEngineName: String) =
selectDefaultSearchEngine(searchEngineName)
fun clickSearchEngineButton() {
val searchEngineButton = mDevice.findObject(UiSelector()
.instance(1)
.className(ToggleButton::class.java))
searchEngineButton.waitForExists(waitingTime)
searchEngineButton.click()
}
fun clickScanButton() {
scanButton().perform(click())
}
@ -106,6 +130,11 @@ class SearchRobot {
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun goToSearchEngine(interact: NavigationToolbarRobot.() -> Unit): NavigationToolbarRobot.Transition {
NavigationToolbarRobot().interact()
return NavigationToolbarRobot.Transition()
}
}
}
@ -178,4 +207,46 @@ fun searchScreen(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
return SearchRobot.Transition()
}
private fun assertKeyboardVisibility(isExpectedToBeVisible: Boolean) = {
mDevice.waitNotNull(
Until.findObject(
By.text("Search Engine")
), waitingTime
)
assertEquals(
isExpectedToBeVisible,
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
.executeShellCommand("dumpsys input_method | grep mInputShown")
.contains("mInputShown=true")
)
}
private fun assertSearchEngineList() {
onView(withId(R.id.mozac_browser_toolbar_edit_icon)).click()
onView(withText("Google"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withText("Amazon.com"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withText("Bing"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withText("DuckDuckGo"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withText("Twitter"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withText("Wikipedia"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun selectDefaultSearchEngine(searchEngine: String) {
onView(withId(R.id.mozac_browser_toolbar_edit_icon)).click()
onView(withText(searchEngine))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
.perform(click())
}
private fun assertDefaultSearchEngine(expectedText: String) {
onView(allOf(withId(R.id.mozac_browser_toolbar_edit_icon), withContentDescription(expectedText)))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun goBackButton() = onView(allOf(withContentDescription("Navigate up")))

View File

@ -228,13 +228,8 @@
<activity android:name=".settings.account.AuthIntentReceiverActivity"
android:exported="false" />
<activity android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
android:exported="false"
android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar"/>
<activity android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
android:exported="false"
android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar"/>
<activity android:name=".settings.about.AboutLibrariesActivity"
android:exported="false" />
<service android:name=".media.MediaService"
android:exported="false" />

View File

@ -29,11 +29,6 @@ object FeatureFlags {
*/
val tabTray = Config.channel.isNightlyOrDebug
/**
* Enables gestures on the browser chrome that depend on a [SwipeGestureLayout]
*/
val browserChromeGestures = Config.channel.isNightlyOrDebug
/**
* Enables viewing tab history
*/

View File

@ -168,11 +168,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
require(arguments != null)
customTabSessionId = arguments?.getString(EXTRA_SESSION_ID)
val view = if (FeatureFlags.browserChromeGestures) {
inflater.inflate(R.layout.browser_gesture_wrapper, container, false)
} else {
inflater.inflate(R.layout.fragment_browser, container, false)
}
val view = inflater.inflate(R.layout.fragment_browser, container, false)
val activity = activity as HomeActivity
activity.themeManager.applyStatusBarTheme(activity)

View File

@ -15,7 +15,6 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.browser_gesture_wrapper.*
import kotlinx.android.synthetic.main.fragment_browser.*
import kotlinx.android.synthetic.main.fragment_browser.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -32,7 +31,6 @@ import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.tabs.WindowFeature
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.addons.runIfFragmentIsAttached
import org.mozilla.fenix.components.FenixSnackbar
@ -77,19 +75,15 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
val components = context.components
return super.initializeUI(view)?.also {
// We need to wrap this whole thing in an if here because gestureLayout will not exist
// if the feature flag is off
if (FeatureFlags.browserChromeGestures) {
gestureLayout.addGestureListener(
ToolbarGestureHandler(
activity = requireActivity(),
contentLayout = browserLayout,
tabPreview = tabPreview,
toolbarLayout = browserToolbarView.view,
sessionManager = components.core.sessionManager
)
gestureLayout.addGestureListener(
ToolbarGestureHandler(
activity = requireActivity(),
contentLayout = browserLayout,
tabPreview = tabPreview,
toolbarLayout = browserToolbarView.view,
sessionManager = components.core.sessionManager
)
}
)
val readerModeAction =
BrowserToolbar.ToggleButton(

View File

@ -6,19 +6,19 @@ package org.mozilla.fenix.browser
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.Activity
import android.graphics.PointF
import android.graphics.Rect
import android.util.TypedValue
import android.view.View
import android.view.ViewConfiguration
import androidx.annotation.Dimension
import androidx.annotation.Dimension.DP
import androidx.core.animation.doOnEnd
import androidx.core.graphics.contains
import androidx.core.graphics.toPoint
import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FlingAnimation
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.support.ktx.android.util.dpToPx
@ -61,11 +61,6 @@ class ToolbarGestureHandler(
private val touchSlop = ViewConfiguration.get(activity).scaledTouchSlop
private val minimumFlingVelocity = ViewConfiguration.get(activity).scaledMinimumFlingVelocity
private val defaultVelocity = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
MINIMUM_ANIMATION_VELOCITY,
activity.resources.displayMetrics
)
private var gestureDirection = GestureDirection.LEFT_TO_RIGHT
@ -143,25 +138,12 @@ class ToolbarGestureHandler(
) {
val destination = getDestination()
if (destination is Destination.Tab && isGestureComplete(velocityX)) {
animateToNextTab(velocityX, destination.session)
animateToNextTab(destination.session)
} else {
animateCanceledGesture(velocityX)
}
}
private fun createFlingAnimation(
view: View,
minValue: Float,
maxValue: Float,
startVelocity: Float
): FlingAnimation =
FlingAnimation(view, DynamicAnimation.TRANSLATION_X).apply {
setMinValue(minValue)
setMaxValue(maxValue)
setStartVelocity(startVelocity)
friction = ViewConfiguration.getScrollFriction()
}
private fun getDestination(): Destination {
val isLtr = activity.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
val currentSession = sessionManager.selectedSession ?: return Destination.None
@ -234,73 +216,59 @@ class ToolbarGestureHandler(
abs(velocityX) >= minimumFlingVelocity)
}
private fun getVelocityFromFling(velocityX: Float): Float {
return max(abs(velocityX), defaultVelocity)
private fun getAnimator(finalContextX: Float, duration: Long): ValueAnimator {
return ValueAnimator.ofFloat(contentLayout.translationX, finalContextX).apply {
this.duration = duration
this.interpolator = LinearOutSlowInInterpolator()
addUpdateListener { animator ->
val value = animator.animatedValue as Float
contentLayout.translationX = value
tabPreview.translationX = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> value + windowWidth + previewOffset
GestureDirection.LEFT_TO_RIGHT -> value - windowWidth - previewOffset
}
}
}
}
private fun animateToNextTab(velocityX: Float, session: Session) {
private fun animateToNextTab(session: Session) {
val browserFinalXCoordinate: Float = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> -windowWidth.toFloat() - previewOffset
GestureDirection.LEFT_TO_RIGHT -> windowWidth.toFloat() + previewOffset
}
val animationVelocity = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> -getVelocityFromFling(velocityX)
GestureDirection.LEFT_TO_RIGHT -> getVelocityFromFling(velocityX)
}
// Finish animating the contentLayout off screen and tabPreview on screen
createFlingAnimation(
view = contentLayout,
minValue = min(0f, browserFinalXCoordinate),
maxValue = max(0f, browserFinalXCoordinate),
startVelocity = animationVelocity
).addUpdateListener { _, value, _ ->
tabPreview.translationX = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> value + windowWidth + previewOffset
GestureDirection.LEFT_TO_RIGHT -> value - windowWidth - previewOffset
}
}.addEndListener { _, _, _, _ ->
contentLayout.translationX = 0f
sessionManager.select(session)
getAnimator(browserFinalXCoordinate, FINISHED_GESTURE_ANIMATION_DURATION).apply {
doOnEnd {
contentLayout.translationX = 0f
sessionManager.select(session)
// Fade out the tab preview to prevent flickering
val shortAnimationDuration =
activity.resources.getInteger(android.R.integer.config_shortAnimTime)
tabPreview.animate()
.alpha(0f)
.setDuration(shortAnimationDuration.toLong())
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
tabPreview.isVisible = false
}
})
// Fade out the tab preview to prevent flickering
val shortAnimationDuration =
activity.resources.getInteger(android.R.integer.config_shortAnimTime)
tabPreview.animate()
.alpha(0f)
.setDuration(shortAnimationDuration.toLong())
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
tabPreview.isVisible = false
}
})
}
}.start()
}
private fun animateCanceledGesture(gestureVelocity: Float) {
val velocity = if (getDestination() is Destination.None) {
defaultVelocity
private fun animateCanceledGesture(velocityX: Float) {
val duration = if (abs(velocityX) >= minimumFlingVelocity) {
CANCELED_FLING_ANIMATION_DURATION
} else {
getVelocityFromFling(gestureVelocity)
}.let { v ->
when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> v
GestureDirection.LEFT_TO_RIGHT -> -v
}
CANCELED_GESTURE_ANIMATION_DURATION
}
createFlingAnimation(
view = contentLayout,
minValue = min(0f, contentLayout.translationX),
maxValue = max(0f, contentLayout.translationX),
startVelocity = velocity
).addUpdateListener { _, value, _ ->
tabPreview.translationX = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> value + windowWidth + previewOffset
GestureDirection.LEFT_TO_RIGHT -> value - windowWidth - previewOffset
getAnimator(0f, duration).apply {
doOnEnd {
tabPreview.isVisible = false
}
}.addEndListener { _, _, _, _ ->
tabPreview.isVisible = false
}.start()
}
@ -336,16 +304,25 @@ class ToolbarGestureHandler(
*/
private const val OVERSCROLL_HIDE_PERCENT = 0.20
/**
* The speed of the fling animation (in dp per second).
*/
@Dimension(unit = DP)
private const val MINIMUM_ANIMATION_VELOCITY = 1500f
/**
* The size of the gap between the tab preview and content layout.
*/
@Dimension(unit = DP)
private const val PREVIEW_OFFSET = 48
/**
* Animation duration when switching to another tab
*/
private const val FINISHED_GESTURE_ANIMATION_DURATION = 250L
/**
* Animation duration gesture is canceled due to the swipe not being far enough
*/
private const val CANCELED_GESTURE_ANIMATION_DURATION = 200L
/**
* Animation duration gesture is canceled due to a swipe in the opposite direction
*/
private const val CANCELED_FLING_ANIMATION_DURATION = 150L
}
}

View File

@ -5,6 +5,7 @@
package org.mozilla.fenix.components.metrics
import androidx.annotation.VisibleForTesting
import com.leanplum.Leanplum
import mozilla.components.browser.awesomebar.facts.BrowserAwesomeBarFacts
import mozilla.components.browser.menu.facts.BrowserMenuFacts
import mozilla.components.browser.toolbar.facts.ToolbarFacts
@ -193,6 +194,7 @@ internal class ReleaseMetricController(
if (installedAddons is List<*>) {
Addons.installedAddons.set(installedAddons.map { it.toString() })
Addons.hasInstalledAddons.set(installedAddons.size > 0)
Leanplum.setUserAttributes(mapOf("installed_addons" to installedAddons.size))
}
}
@ -200,6 +202,7 @@ internal class ReleaseMetricController(
if (enabledAddons is List<*>) {
Addons.enabledAddons.set(enabledAddons.map { it.toString() })
Addons.hasEnabledAddons.set(enabledAddons.size > 0)
Leanplum.setUserAttributes(mapOf("enabled_addons" to enabledAddons.size))
}
}

View File

@ -13,7 +13,6 @@ import android.view.ViewGroup
import androidx.core.content.pm.PackageInfoCompat
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DividerItemDecoration
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import kotlinx.android.synthetic.main.fragment_about.*
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.BuildConfig
@ -168,13 +167,8 @@ class AboutFragment : Fragment(), AboutPageListener {
}
private fun openLibrariesPage() {
startActivity(Intent(context, OssLicensesMenuActivity::class.java))
OssLicensesMenuActivity.setActivityTitle(
getString(
R.string.open_source_licenses_title,
appName
)
)
val intent = Intent(requireContext(), AboutLibrariesActivity::class.java)
startActivity(intent)
}
override fun onAboutItemClicked(item: AboutItem) {

View File

@ -0,0 +1,120 @@
/* 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.about
import android.graphics.Typeface
import android.os.Bundle
import android.text.util.Linkify
import android.widget.ArrayAdapter
import android.widget.ListView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import org.mozilla.fenix.R
import java.nio.charset.Charset
import java.util.Locale
/**
* Displays the licenses of all the libraries used by Fenix.
*
* This is a re-implementation of play-services-oss-licenses library.
* We can't use the official implementation in the OSS flavor of Fenix
* because it is proprietary and closed-source.
*
* There are popular FLOSS alternatives to Google's plugin and library
* such as AboutLibraries (https://github.com/mikepenz/AboutLibraries)
* but we considered the risk of introducing such third-party dependency
* to Fenix too high. Therefore, we use Google's gradle plugin to
* extract the dependencies and their licenses, and this activity
* to show the extracted licenses to the end-user.
*/
class AboutLibrariesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val appName = getString(R.string.app_name)
title = getString(R.string.open_source_licenses_title, appName)
setContentView(R.layout.about_libraries_activity)
setSupportActionBar(findViewById(R.id.toolbar))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
setupLibrariesListView()
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
private fun setupLibrariesListView() {
val libraries = parseLibraries()
val listView = findViewById<ListView>(R.id.about_libraries_listview)
listView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, libraries)
listView.setOnItemClickListener { _, _, position, _ ->
showLicenseDialog(libraries[position])
}
}
private fun parseLibraries(): List<LibraryItem> {
/*
The gradle plugin "oss-licenses-plugin" creates two "raw" resources:
- third_party_licenses which is the binary concatenation of all the licenses text for
all the libraries. License texts can either be an URL to a license file or just the
raw text of the license.
- third_party_licenses_metadata which contains one dependency per line formatted in
the following way: "[start_offset]:[length] [name]"
[start_offset] : first byte in third_party_licenses that contains the license
text for this library.
[length] : length of the license text for this library in
third_party_licenses.
[name] : either the name of the library, or its artifact name.
See https://github.com/google/play-services-plugins/tree/master/oss-licenses-plugin
*/
val licensesData = resources
.openRawResource(R.raw.third_party_licenses)
.readBytes()
val licensesMetadataReader = resources
.openRawResource(R.raw.third_party_license_metadata)
.bufferedReader()
return licensesMetadataReader.use { reader -> reader.readLines() }.map { line ->
val (section, name) = line.split(" ", limit = 2)
val (startOffset, length) = section.split(":", limit = 2).map(String::toInt)
val licenseData = licensesData.sliceArray(startOffset until startOffset + length)
val licenseText = licenseData.toString(Charset.forName("UTF-8"))
LibraryItem(name, licenseText)
}.sortedBy { item -> item.name.toLowerCase(Locale.ROOT) }
}
private fun showLicenseDialog(libraryItem: LibraryItem) {
val dialog = AlertDialog.Builder(this)
.setTitle(libraryItem.name)
.setMessage(libraryItem.license)
.create()
dialog.show()
val textView = dialog.findViewById<TextView>(android.R.id.message)!!
Linkify.addLinks(textView, Linkify.ALL)
textView.linksClickable = true
textView.textSize = LICENSE_TEXT_SIZE
textView.typeface = Typeface.MONOSPACE
}
companion object {
private const val LICENSE_TEXT_SIZE = 10F
}
}
private class LibraryItem(val name: String, val license: String) {
override fun toString(): String {
return name
}
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/about_libraries"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
tools:context="org.mozilla.fenix.settings.about.AboutLibrariesActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar" />
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/about_libraries_listview" />
</RelativeLayout>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<org.mozilla.fenix.browser.SwipeGestureLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/gestureLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/fragment_browser" />
<org.mozilla.fenix.browser.TabPreview
android:id="@+id/tabPreview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="false"
android:focusable="false"
android:visibility="gone" />
</org.mozilla.fenix.browser.SwipeGestureLayout>

View File

@ -1,53 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<org.mozilla.fenix.browser.SwipeGestureLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/browserLayout"
android:id="@+id/gestureLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="browser.BrowserFragment">
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/browserLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
tools:context="browser.BrowserFragment">
<mozilla.components.concept.engine.EngineView
android:id="@+id/engineView"
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<mozilla.components.concept.engine.EngineView
android:id="@+id/engineView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<ViewStub
android:id="@+id/stubFindInPage"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_gravity="bottom"
android:inflatedId="@+id/findInPageView"
android:layout="@layout/stub_find_in_page" />
<include
android:id="@+id/viewDynamicDownloadDialog"
layout="@layout/download_dialog_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:visibility="gone" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<mozilla.components.feature.readerview.view.ReaderViewControlsBar
android:id="@+id/readerViewControlsBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?foundation"
android:elevation="24dp"
android:visibility="gone" />
<ViewStub
android:id="@+id/stubFindInPage"
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<org.mozilla.fenix.browser.TabPreview
android:id="@+id/tabPreview"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_gravity="bottom"
android:inflatedId="@+id/findInPageView"
android:layout="@layout/stub_find_in_page" />
<include
android:id="@+id/viewDynamicDownloadDialog"
layout="@layout/download_dialog_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_height="match_parent"
android:clickable="false"
android:focusable="false"
android:visibility="gone" />
<mozilla.components.feature.readerview.view.ReaderViewControlsBar
android:id="@+id/readerViewControlsBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?foundation"
android:elevation="24dp"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</org.mozilla.fenix.browser.SwipeGestureLayout>

View File

@ -306,6 +306,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
<!-- Preference for open links in third party apps -->
<string name="preferences_open_links_in_apps">Ldi iseɣwan deg isnasen</string>
<!-- Preference for open download with an external download manager app -->
<string name="preferences_external_download_manager">Amsefrak n usider azɣaray</string>
<!-- Preference for add_ons -->
<string name="preferences_addons">Izegrar</string>

View File

@ -30,16 +30,16 @@
<!-- Tab tray multi select title in app bar. The first parameter is the number of tabs selected -->
<string name="tab_tray_multi_select_title">%1$d개 선택됨</string>
<!-- Label of button in create collection dialog for creating a new collection -->
<string name="tab_tray_add_new_collection">컬렉션 추가</string>
<string name="tab_tray_add_new_collection">모음집 추가</string>
<!-- Label of editable text in create collection dialog for naming a new collection -->
<string name="tab_tray_add_new_collection_name">이름</string>
<!-- Label of button in save to collection dialog for selecting a current collection -->
<string name="tab_tray_select_collection">컬렉션 선택</string>
<string name="tab_tray_select_collection">모음집 선택</string>
<!-- Content description for close button while in multiselect mode in tab tray -->
<string name="tab_tray_close_multiselect_content_description">다중 선택 모드 종료</string>
<!-- Content description for save to collection button while in multiselect mode in tab tray -->
<string name="tab_tray_collection_button_multiselect_content_description">선택한 탭을 컬렉션에 저장</string>
<string name="tab_tray_collection_button_multiselect_content_description">선택한 탭을 모음집에 저장</string>
<!-- Content description for checkmark while tab is selected while in multiselect mode in tab tray. The first parameter is the title of the tab selected -->
<string name="tab_tray_item_selected_multiselect_content_description">%1$s 선택됨</string>
@ -49,7 +49,7 @@
<!-- Content description announcement when exiting multiselect mode in tab tray -->
<string name="tab_tray_exit_multiselect_content_description">다중 선택 모드 종료됨</string>
<!-- Content description announcement when entering multiselect mode in tab tray -->
<string name="tab_tray_enter_multiselect_content_description">다중 선택 모드로 전환됨, 컬렉션에 저장할 탭을 선택하세요</string>
<string name="tab_tray_enter_multiselect_content_description">다중 선택 모드로 전환됨, 모음집에 저장할 탭을 선택하세요</string>
<!-- Content description on checkmark while tab is selected in multiselect mode in tab tray -->
<string name="tab_tray_multiselect_selected_content_description">선택됨</string>
@ -137,7 +137,7 @@
<!-- Browser menu button that creates a new tab -->
<string name="browser_menu_new_tab">새 탭</string>
<!-- Browser menu button that saves the current tab to a collection -->
<string name="browser_menu_save_to_collection_2">컬렉션에 저장</string>
<string name="browser_menu_save_to_collection_2">모음집에 저장</string>
<!-- Browser menu button that open a share menu to share the current site -->
<string name="browser_menu_share">공유</string>
<!-- Share menu title, displayed when a user is sharing their current site -->
@ -497,7 +497,7 @@
<!-- Text shown as the title of the open tab tray -->
<string name="tab_tray_title">탭 열기</string>
<!-- Text shown in the menu for saving tabs to a collection -->
<string name="tab_tray_menu_item_save">컬렉션에 저장</string>
<string name="tab_tray_menu_item_save">모음집에 저장</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">모든 탭 공유</string>
<!-- Text shown in the menu for closing all tabs -->
@ -509,7 +509,7 @@
<!-- Shortcut action to toggle private mode -->
<string name="tab_tray_menu_toggle">탭 모드 전환</string>
<!-- Content description (not visible, for screen readers etc.): Removes tab from collection button. Removes the selected tab from collection when pressed -->
<string name="remove_tab_from_collection">컬렉션에서 탭 삭제</string>
<string name="remove_tab_from_collection">모음집에서 탭 삭제</string>
<!-- Content description (not visible, for screen readers etc.): Close tab button. Closes the current session when pressed -->
<string name="close_tab">탭 닫기</string>
<!-- Content description (not visible, for screen readers etc.): Close tab <title> button. First parameter is tab title -->
@ -521,7 +521,7 @@
<!-- Open tabs menu item to share all tabs -->
<string name="tabs_menu_share_tabs">탭 공유</string>
<!-- Open tabs menu item to save tabs to collection -->
<string name="tabs_menu_save_to_collection1">탭을 컬렉션에 저장</string>
<string name="tabs_menu_save_to_collection1">탭을 모음집에 저장</string>
<!-- Content description (not visible, for screen readers etc.): Opens the tab menu when pressed -->
<string name="tab_menu">탭 메뉴</string>
<!-- Tab menu item to share the tab -->
@ -536,11 +536,11 @@
<string name="current_session_image">현재 세션 이미지</string>
<!-- Button to save the current set of tabs into a collection -->
<string name="save_to_collection">컬렉션에 저장</string>
<string name="save_to_collection">모음집에 저장</string>
<!-- Text for the menu button to delete a collection -->
<string name="collection_delete">컬렉션 삭제</string>
<string name="collection_delete">모음집 삭제</string>
<!-- Text for the menu button to rename a collection -->
<string name="collection_rename">컬렉션 이름 변경</string>
<string name="collection_rename">모음집 이름 변경</string>
<!-- Text for the button to open tabs of the selected collection -->
<string name="collection_open_tabs">열린 탭</string>
@ -751,25 +751,25 @@
<!-- Collections -->
<!-- Collections header on home fragment -->
<string name="collections_header">컬렉션</string>
<string name="collections_header">모음집</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">컬렉션 메뉴</string>
<string name="collection_menu_button_content_description">모음집 메뉴</string>
<!-- No Open Tabs Message Header -->
<string name="no_collections_header1">중요한 것들을 모으세요</string>
<string name="no_collections_header1">중요한 것들 수집하기</string>
<!-- Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">나중에 빠르게 액세스 할 수 있도록 유사한 검색, 사이트 및 탭을 그룹화하세요.</string>
<string name="no_collections_description1">나중에 빠르게 접근할 수 있도록 유사한 검색, 사이트 및 탭을 모아 보세요.</string>
<!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">탭 선택</string>
<!-- Title for the "select collection" step of the collection creator -->
<string name="create_collection_select_collection">컬렉션 선택</string>
<string name="create_collection_select_collection">모음집 선택</string>
<!-- Title for the "name collection" step of the collection creator -->
<string name="create_collection_name_collection">컬렉션 이름</string>
<string name="create_collection_name_collection">모음집 이름</string>
<!-- Button to add new collection for the "select collection" step of the collection creator -->
<string name="create_collection_add_new_collection">컬렉션 추가</string>
<string name="create_collection_add_new_collection">모음집 추가</string>
<!-- Button to select all tabs in the "select tabs" step of the collection creator -->
<string name="create_collection_select_all">모두 선택</string>
@ -790,7 +790,7 @@
<string name="create_collection_tabs_saved">탭이 저장되었습니다!</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">컬렉션 저장됨!</string>
<string name="create_collection_tabs_saved_new_collection">모음집 저장됨!</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">탭이 저장되었습니다!</string>
@ -804,7 +804,7 @@
<string name="create_collection_view">보기</string>
<!-- Default name for a new collection in "name new collection" step of the collection creator. %d is a placeholder for the number of collections-->
<string name="create_collection_default_name">컬렉션 %d개</string>
<string name="create_collection_default_name">모음집 %d개</string>
<!-- Share -->
<!-- Share screen header -->
@ -864,10 +864,10 @@
<!-- Name of the "Powered by Fenix" notification channel. Displayed in the "App notifications" system settings for the app -->
<string name="notification_powered_by_channel_name">제공:</string>
<!-- Text shown in snackbar when user deletes a collection -->
<string name="snackbar_collection_deleted">컬렉션 삭제됨</string>
<string name="snackbar_collection_deleted">모음집 삭제됨</string>
<!-- Text shown in snackbar when user renames a collection -->
<string name="snackbar_collection_renamed">컬렉션 이름 변경됨</string>
<string name="snackbar_collection_renamed">모음집 이름 변경됨</string>
<!-- Text shown in snackbar when user deletes a tab -->
<string name="snackbar_tab_deleted">탭 삭제됨</string>
@ -904,9 +904,9 @@
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">%1$s 파일을 삭제하시겠습니까?</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">이 탭을 삭제하면 전체 컬렉션이 삭제됩니다. 언제든지 새 컬렉션을 만들 수 있습니다.</string>
<string name="delete_tab_and_collection_dialog_message">이 탭을 삭제하면 전체 모음집이 삭제됩니다. 언제든지 새 모음집을 만들 수 있습니다.</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">%1$s 컬렉션을 삭제하시겠습니까?</string>
<string name="delete_tab_and_collection_dialog_title">%1$s 모음집을 삭제하시겠습니까?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">삭제</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->

View File

@ -40,6 +40,8 @@
<string name="tab_tray_collection_button_multiselect_content_description">Geselecteerde tabbladen in collectie opslaan</string>
<!-- Content description for checkmark while tab is selected while in multiselect mode in tab tray. The first parameter is the title of the tab selected -->
<string name="tab_tray_item_selected_multiselect_content_description">%1$s geselecteerd</string>
<!-- Content description when tab is unselected while in multiselect mode in tab tray. The first parameter is the title of the tab unselected -->
<string name="tab_tray_item_unselected_multiselect_content_description">Selectie %1$s ongedaan gemaakt</string>
<!-- Content description on checkmark while tab is selected in multiselect mode in tab tray -->
<string name="tab_tray_multiselect_selected_content_description">Geselecteerd</string>

View File

@ -0,0 +1,45 @@
/* 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.about
import android.widget.ListView
import android.widget.TextView
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.robolectric.Robolectric
import org.robolectric.Shadows.shadowOf
import org.robolectric.shadows.ShadowAlertDialog
@RunWith(FenixRobolectricTestRunner::class)
class AboutLibrariesActivityTest {
@Test
fun `activity should display licenses`() {
val activity = Robolectric.buildActivity(AboutLibrariesActivity::class.java).create().get()
val listView = activity.findViewById<ListView>(R.id.about_libraries_listview)
assertTrue(0 < listView.count)
}
@Test
fun `item click should open license dialog`() {
val activity = Robolectric.buildActivity(AboutLibrariesActivity::class.java).create().get()
val listView = activity.findViewById<ListView>(R.id.about_libraries_listview)
val listViewShadow = shadowOf(listView)
listViewShadow.clickFirstItemContainingText("org.mozilla.geckoview:geckoview")
val alertDialogShadow = ShadowAlertDialog.getLatestDialog()
assertTrue(alertDialogShadow.isShowing)
val alertDialogText = alertDialogShadow
.findViewById<TextView>(android.R.id.message)
.text
.toString()
assertTrue(alertDialogText.contains("MPL"))
}
}

View File

@ -3,5 +3,5 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
object AndroidComponents {
const val VERSION = "54.0.20200814130102"
const val VERSION = "54.0.20200818130156"
}

View File

@ -10,7 +10,6 @@ object Versions {
const val leakcanary = "2.4"
const val leanplum = "5.4.0"
const val osslicenses_plugin = "0.9.5"
const val osslicenses_library = "17.0.0"
const val detekt = "1.9.1"
const val androidx_appcompat = "1.2.0-rc01"
@ -28,7 +27,6 @@ object Versions {
const val androidx_paging = "2.1.0"
const val androidx_transition = "1.3.0"
const val androidx_work = "2.2.0"
const val androidx_dynamic_animation = "1.0.0"
const val google_material = "1.1.0"
const val google_flexbox = "2.0.1"
@ -59,7 +57,6 @@ object Deps {
const val allopen = "org.jetbrains.kotlin:kotlin-allopen:${Versions.kotlin}"
const val osslicenses_plugin = "com.google.android.gms:oss-licenses-plugin:${Versions.osslicenses_plugin}"
const val osslicenses_library = "com.google.android.gms:play-services-oss-licenses:${Versions.osslicenses_library}"
const val mozilla_concept_engine = "org.mozilla.components:concept-engine:${Versions.mozilla_android_components}"
const val mozilla_concept_menu = "org.mozilla.components:concept-menu:${Versions.mozilla_android_components}"
@ -174,7 +171,6 @@ object Deps {
const val androidx_recyclerview = "androidx.recyclerview:recyclerview:${Versions.androidx_recyclerview}"
const val androidx_core = "androidx.core:core:${Versions.androidx_core}"
const val androidx_core_ktx = "androidx.core:core-ktx:${Versions.androidx_core}"
const val androidx_dynamic_animation = "androidx.dynamicanimation:dynamicanimation:${Versions.androidx_dynamic_animation}"
const val androidx_transition = "androidx.transition:transition:${Versions.androidx_transition}"
const val androidx_work_ktx = "androidx.work:work-runtime-ktx:${Versions.androidx_work}"
const val androidx_work_testing = "androidx.work:work-testing:${Versions.androidx_work}"

View File

@ -159,6 +159,16 @@ User Attributes
<td>A boolean indicating that this is a Fenix installation</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/8208">#8208</a></td>
</tr>
<tr>
<td>`installed_addons`</td>
<td>A boolean indicating that there are addons installed</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/13233">#13233</a></td>
</tr>
<tr>
<td>`enabled_addons`</td>
<td>A boolean indicating that there are addons enabled</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/13233">#13233</a></td>
</tr>
</table>
Events