Merge tag 'v89.1.1' into upstream-sync
commit
c667cfa0ae
@ -0,0 +1,43 @@
|
||||
# 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: "Sync Strings"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 */4 * * *'
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: "Sync Strings"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: "Discover Fenix Beta Version"
|
||||
id: fenix-beta-version
|
||||
uses: mozilla-mobile/fenix-beta-version@1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: "Checkout Master Branch"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: main
|
||||
ref: master
|
||||
- name: "Checkout Beta Branch"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: beta
|
||||
ref: "releases_v${{ steps.fenix-beta-version.outputs.fenix-beta-version }}.0.0"
|
||||
- name: "Sync Strings"
|
||||
uses: mozilla-mobile/sync-strings-action@1.0.1
|
||||
with:
|
||||
src: main
|
||||
dst: beta
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path: beta
|
||||
branch: automation/sync-strings-${{ steps.fenix-beta-version.outputs.major-beta-version }}
|
||||
title: "Sync Strings from master to releases_${{steps.fenix-beta-version.outputs.fenix-beta-version}}.0"
|
||||
body: "This (automated) PR syncs strings from `master` to `releases_${{steps.fenix-beta-version.outputs.fenix-beta-version}}.0.0`"
|
@ -0,0 +1,52 @@
|
||||
/* 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.robots
|
||||
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.espresso.matcher.ViewMatchers.Visibility
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import org.hamcrest.CoreMatchers.allOf
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.helpers.click
|
||||
|
||||
/**
|
||||
* Implementation of Robot Pattern for Sync Sign In sub menu.
|
||||
*/
|
||||
class SyncSignInRobot {
|
||||
|
||||
fun verifyAccountSettingsMenuHeader() = assertAccountSettingsMenuHeader()
|
||||
fun verifySyncSignInMenuHeader() = assertSyncSignInMenuHeader()
|
||||
|
||||
class Transition {
|
||||
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())!!
|
||||
|
||||
fun goBack(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||
goBackButton().click()
|
||||
|
||||
BrowserRobot().interact()
|
||||
return BrowserRobot.Transition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun goBackButton() =
|
||||
onView(allOf(withContentDescription("Navigate up")))
|
||||
|
||||
private fun assertAccountSettingsMenuHeader() {
|
||||
// Replaced with the new string here, the test is assuming we are NOT signed in
|
||||
// Sync tests in SettingsSyncTest are still TO-DO, so I'm not sure that we have a test for signing into Sync
|
||||
onView(withText(R.string.preferences_account_settings))
|
||||
.check((matches(withEffectiveVisibility(Visibility.VISIBLE))))
|
||||
}
|
||||
|
||||
private fun assertSyncSignInMenuHeader() {
|
||||
onView(withText(R.string.sign_in_with_camera))
|
||||
.check((matches(withEffectiveVisibility(Visibility.VISIBLE))))
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/* 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.util
|
||||
|
||||
const val STRING_ONBOARDING_ACCOUNT_SIGN_IN_HEADER = "Sync Firefox between devices"
|
||||
const val STRING_ONBOARDING_TRACKING_PROTECTION_HEADER = "Always-on privacy"
|
||||
const val STRING_ONBOARDING_TOOLBAR_PLACEMENT_HEADER = "Pick your toolbar placement"
|
@ -0,0 +1,25 @@
|
||||
/* 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.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.os.Bundle
|
||||
|
||||
/**
|
||||
* An inheritance of [Application.ActivityLifecycleCallbacks] where each method has a default
|
||||
* implementation that does nothing. This allows classes that extend this interface to have
|
||||
* more concise definitions if they don't implement some methods; this is in the spirit of
|
||||
* other `Default*` classes, such as [androidx.lifecycle.DefaultLifecycleObserver].
|
||||
*/
|
||||
interface DefaultActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {
|
||||
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
|
||||
override fun onActivityStarted(activity: Activity) {}
|
||||
override fun onActivityResumed(activity: Activity) {}
|
||||
override fun onActivityPaused(activity: Activity) {}
|
||||
override fun onActivityStopped(activity: Activity) {}
|
||||
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
|
||||
override fun onActivityDestroyed(activity: Activity) {}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/* 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.collections
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.EditText
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import mozilla.components.feature.tab.collections.TabCollection
|
||||
import mozilla.components.support.ktx.android.view.showKeyboard
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.TabCollectionStorage
|
||||
import org.mozilla.fenix.ext.getDefaultCollectionNumber
|
||||
|
||||
/**
|
||||
* A lambda that is invoked when a confirmation button in a [CollectionsDialog] is clicked.
|
||||
*
|
||||
* A [TabCollection] of the selected collected is passed to the delegate when confirmed. If null,
|
||||
* then a new collection is created.
|
||||
*
|
||||
* A list of [TabSessionState] is returned that will be put into the collections storage.
|
||||
*/
|
||||
typealias OnPositiveButtonClick = (collection: TabCollection?) -> List<TabSessionState>
|
||||
|
||||
/**
|
||||
* A lambda that is invoked when a cancel button in a [CollectionsDialog] is clicked.
|
||||
*/
|
||||
typealias OnNegativeButtonClick = () -> Unit
|
||||
|
||||
/**
|
||||
* A data class for creating a dialog to prompt adding/creating a collection. See also [show].
|
||||
*
|
||||
* @property onPositiveButtonClick Invoked when a user clicks on a confirmation button in the dialog.
|
||||
* @property onNegativeButtonClick Invoked when a user clicks on a cancel button in the dialog.
|
||||
*/
|
||||
data class CollectionsDialog(
|
||||
val storage: TabCollectionStorage,
|
||||
val onPositiveButtonClick: OnPositiveButtonClick,
|
||||
val onNegativeButtonClick: OnNegativeButtonClick
|
||||
)
|
||||
|
||||
/**
|
||||
* Create and display a [CollectionsDialog] using [AlertDialog].
|
||||
*/
|
||||
fun CollectionsDialog.show(
|
||||
context: Context
|
||||
) {
|
||||
if (storage.cachedTabCollections.isEmpty()) {
|
||||
showAddNewDialog(context, storage)
|
||||
return
|
||||
}
|
||||
|
||||
val collections = storage.cachedTabCollections.map { it.title }
|
||||
val layout = LayoutInflater.from(context).inflate(R.layout.add_new_collection_dialog, null)
|
||||
val list = layout.findViewById<RecyclerView>(R.id.recycler_view)
|
||||
|
||||
val builder = AlertDialog.Builder(context).setTitle(R.string.tab_tray_select_collection)
|
||||
.setView(layout)
|
||||
.setPositiveButton(android.R.string.ok) { dialog, _ ->
|
||||
val selectedCollection =
|
||||
(list.adapter as CollectionsListAdapter).getSelectedCollection()
|
||||
val collection = storage.cachedTabCollections[selectedCollection]
|
||||
val sessionList = onPositiveButtonClick.invoke(collection)
|
||||
|
||||
MainScope().launch {
|
||||
storage.addTabsToCollection(collection, sessionList)
|
||||
}
|
||||
|
||||
dialog.dismiss()
|
||||
}.setNegativeButton(android.R.string.cancel) { dialog, _ ->
|
||||
onNegativeButtonClick.invoke()
|
||||
|
||||
dialog.cancel()
|
||||
}
|
||||
|
||||
val dialog = builder.create()
|
||||
val collectionNames =
|
||||
arrayOf(context.getString(R.string.tab_tray_add_new_collection)) + collections
|
||||
val collectionsListAdapter = CollectionsListAdapter(collectionNames) {
|
||||
dialog.dismiss()
|
||||
showAddNewDialog(context, storage)
|
||||
}
|
||||
|
||||
list.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = collectionsListAdapter
|
||||
}
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
internal fun CollectionsDialog.showAddNewDialog(
|
||||
context: Context,
|
||||
collectionsStorage: TabCollectionStorage
|
||||
) {
|
||||
val layout = LayoutInflater.from(context).inflate(R.layout.name_collection_dialog, null)
|
||||
val collectionNameEditText: EditText = layout.findViewById(R.id.collection_name)
|
||||
|
||||
collectionNameEditText.setText(
|
||||
context.getString(
|
||||
R.string.create_collection_default_name,
|
||||
collectionsStorage.cachedTabCollections.getDefaultCollectionNumber()
|
||||
)
|
||||
)
|
||||
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(R.string.tab_tray_add_new_collection)
|
||||
.setView(layout).setPositiveButton(android.R.string.ok) { dialog, _ ->
|
||||
val sessionList = onPositiveButtonClick.invoke(null)
|
||||
|
||||
MainScope().launch {
|
||||
storage.createCollection(
|
||||
collectionNameEditText.text.toString(),
|
||||
sessionList
|
||||
)
|
||||
}
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { dialog, _ ->
|
||||
onNegativeButtonClick.invoke()
|
||||
dialog.cancel()
|
||||
}
|
||||
.create()
|
||||
.show()
|
||||
|
||||
collectionNameEditText.setSelection(0, collectionNameEditText.text.length)
|
||||
collectionNameEditText.showKeyboard()
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/* 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.accounts
|
||||
|
||||
import android.content.Context
|
||||
import mozilla.components.service.fxa.manager.FxaAccountManager
|
||||
import org.mozilla.fenix.ext.components
|
||||
|
||||
/**
|
||||
* Component which holds a reference to [FxaAccountManager]. Manages account authentication,
|
||||
* profiles, and profile state observers.
|
||||
*/
|
||||
open class FenixAccountManager(context: Context) {
|
||||
val accountManager = context.components.backgroundServices.accountManager
|
||||
|
||||
val authenticatedAccount
|
||||
get() = accountManager.authenticatedAccount() != null
|
||||
|
||||
val accountProfileEmail
|
||||
get() = accountManager.accountProfile()?.email
|
||||
|
||||
/**
|
||||
* Check if the current account is signed in and authenticated.
|
||||
*/
|
||||
fun signedInToFxa(): Boolean {
|
||||
val account = accountManager.authenticatedAccount()
|
||||
val needsReauth = accountManager.accountNeedsReauth()
|
||||
|
||||
return account != null && !needsReauth
|
||||
}
|
||||
}
|
@ -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.components.bookmarks
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import mozilla.appservices.places.BookmarkRoot
|
||||
import mozilla.components.concept.storage.BookmarksStorage
|
||||
|
||||
/**
|
||||
* Use cases that allow for modifying bookmarks.
|
||||
*/
|
||||
class BookmarksUseCase(storage: BookmarksStorage) {
|
||||
|
||||
class AddBookmarksUseCase internal constructor(private val storage: BookmarksStorage) {
|
||||
|
||||
/**
|
||||
* Adds a new bookmark with the provided [url] and [title].
|
||||
*
|
||||
* @return The result if the operation was executed or not. A bookmark may not be added if
|
||||
* one with the identical [url] already exists.
|
||||
*/
|
||||
@WorkerThread
|
||||
suspend operator fun invoke(url: String, title: String, position: Int? = null): Boolean {
|
||||
val canAdd = storage.getBookmarksWithUrl(url).firstOrNull { it.url == it.url } == null
|
||||
|
||||
if (canAdd) {
|
||||
storage.addItem(
|
||||
BookmarkRoot.Mobile.id,
|
||||
url = url,
|
||||
title = title,
|
||||
position = position
|
||||
)
|
||||
}
|
||||
|
||||
return canAdd
|
||||
}
|
||||
}
|
||||
|
||||
val addBookmark by lazy { AddBookmarksUseCase(storage) }
|
||||
}
|
@ -1,247 +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.metrics
|
||||
|
||||
import android.app.Application
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.leanplum.Leanplum
|
||||
import com.leanplum.LeanplumActivityHelper
|
||||
import com.leanplum.annotations.Parser
|
||||
import com.leanplum.internal.LeanplumInternal
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import mozilla.components.support.locale.LocaleManager
|
||||
import org.mozilla.fenix.BuildConfig
|
||||
import org.mozilla.fenix.components.metrics.MozillaProductDetector.MozillaProducts
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.home.intent.DeepLinkIntentProcessor
|
||||
import java.util.Locale
|
||||
import java.util.MissingResourceException
|
||||
import java.util.UUID.randomUUID
|
||||
|
||||
private val Event.name: String?
|
||||
get() = when (this) {
|
||||
is Event.AddBookmark -> "E_Add_Bookmark"
|
||||
is Event.RemoveBookmark -> "E_Remove_Bookmark"
|
||||
is Event.OpenedBookmark -> "E_Opened_Bookmark"
|
||||
is Event.OpenedApp -> "E_Opened_App"
|
||||
is Event.OpenedAppFirstRun -> "E_Opened_App_FirstRun"
|
||||
is Event.InteractWithSearchURLArea -> "E_Interact_With_Search_URL_Area"
|
||||
is Event.CollectionSaved -> "E_Collection_Created"
|
||||
is Event.CollectionTabRestored -> "E_Collection_Tab_Opened"
|
||||
is Event.SyncAuthSignUp -> "E_FxA_New_Signup"
|
||||
is Event.SyncAuthSignIn, Event.SyncAuthPaired, Event.SyncAuthOtherExternal -> "E_Sign_In_FxA"
|
||||
is Event.SyncAuthFromSharedCopy, Event.SyncAuthFromSharedReuse -> "E_Sign_In_FxA_Fennec_to_Fenix"
|
||||
is Event.SyncAuthSignOut -> "E_Sign_Out_FxA"
|
||||
is Event.ClearedPrivateData -> "E_Cleared_Private_Data"
|
||||
is Event.DismissedOnboarding -> "E_Dismissed_Onboarding"
|
||||
is Event.FennecToFenixMigrated -> "E_Fennec_To_Fenix_Migrated"
|
||||
is Event.AddonInstalled -> "E_Addon_Installed"
|
||||
is Event.SearchWidgetInstalled -> "E_Search_Widget_Added"
|
||||
is Event.ChangedToDefaultBrowser -> "E_Changed_Default_To_Fenix"
|
||||
is Event.TrackingProtectionSettingChanged -> "E_Changed_ETP"
|
||||
|
||||
// Do not track other events in Leanplum
|
||||
else -> null
|
||||
}
|
||||
|
||||
class LeanplumMetricsService(
|
||||
private val application: Application
|
||||
) : MetricsService, DeepLinkIntentProcessor.DeepLinkVerifier {
|
||||
val scope = CoroutineScope(Dispatchers.IO)
|
||||
var leanplumJob: Job? = null
|
||||
|
||||
data class Token(val id: String, val token: String) {
|
||||
enum class Type { Development, Production, Invalid }
|
||||
|
||||
val type by lazy {
|
||||
when {
|
||||
token.take(ProdPrefix.length) == ProdPrefix -> Type.Production
|
||||
token.take(DevPrefix.length) == DevPrefix -> Type.Development
|
||||
else -> Type.Invalid
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ProdPrefix = "prod"
|
||||
private const val DevPrefix = "dev"
|
||||
}
|
||||
}
|
||||
|
||||
override val type = MetricServiceType.Marketing
|
||||
private val token = Token(LeanplumId, LeanplumToken)
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
override fun start() {
|
||||
|
||||
if (!application.settings().isMarketingTelemetryEnabled) return
|
||||
|
||||
Leanplum.setIsTestModeEnabled(false)
|
||||
Leanplum.setApplicationContext(application)
|
||||
Leanplum.setDeviceId(randomUUID().toString())
|
||||
Parser.parseVariables(application)
|
||||
|
||||
leanplumJob = scope.launch {
|
||||
|
||||
val applicationSetLocale = LocaleManager.getCurrentLocale(application)
|
||||
val currentLocale = applicationSetLocale ?: Locale.getDefault()
|
||||
val languageCode =
|
||||
currentLocale.iso3LanguageOrNull
|
||||
?: currentLocale.language.let {
|
||||
if (it.isNotBlank()) {
|
||||
it
|
||||
} else {
|
||||
currentLocale.toString()
|
||||
}
|
||||
}
|
||||
|
||||
if (!isLeanplumEnabled(languageCode)) {
|
||||
Log.i(LOGTAG, "Leanplum is not available for this locale: $languageCode")
|
||||
return@launch
|
||||
}
|
||||
|
||||
when (token.type) {
|
||||
Token.Type.Production -> Leanplum.setAppIdForProductionMode(token.id, token.token)
|
||||
Token.Type.Development -> Leanplum.setAppIdForDevelopmentMode(token.id, token.token)
|
||||
Token.Type.Invalid -> {
|
||||
Log.i(LOGTAG, "Invalid or missing Leanplum token")
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
LeanplumActivityHelper.enableLifecycleCallbacks(application)
|
||||
|
||||
val installedApps = MozillaProductDetector.getInstalledMozillaProducts(application)
|
||||
|
||||
val trackingProtection = application.settings().run {
|
||||
when {
|
||||
!shouldUseTrackingProtection -> "none"
|
||||
useStandardTrackingProtection -> "standard"
|
||||
useStrictTrackingProtection -> "strict"
|
||||
else -> "custom"
|
||||
}
|
||||
}
|
||||
|
||||
Leanplum.start(
|
||||
application, hashMapOf(
|
||||
"default_browser" to MozillaProductDetector.getMozillaBrowserDefault(application)
|
||||
.orEmpty(),
|
||||
"fennec_installed" to installedApps.contains(MozillaProducts.FIREFOX.productName),
|
||||
"focus_installed" to installedApps.contains(MozillaProducts.FOCUS.productName),
|
||||
"klar_installed" to installedApps.contains(MozillaProducts.KLAR.productName),
|
||||
"fxa_signed_in" to application.settings().fxaSignedIn,
|
||||
"fxa_has_synced_items" to application.settings().fxaHasSyncedItems,
|
||||
"search_widget_installed" to application.settings().searchWidgetInstalled,
|
||||
"tracking_protection_enabled" to application.settings().shouldUseTrackingProtection,
|
||||
"tracking_protection_setting" to trackingProtection,
|
||||
"fenix" to true
|
||||
)
|
||||
)
|
||||
|
||||
withContext(Main) {
|
||||
LeanplumInternal.setCalledStart(true)
|
||||
LeanplumInternal.setHasStarted(true)
|
||||
LeanplumInternal.setStartedInBackground(true)
|
||||
Log.i(LOGTAG, "Started Leanplum with deviceId ${Leanplum.getDeviceId()}" +
|
||||
" and userId ${Leanplum.getUserId()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a deep link and returns `true` for deep links that should be handled and `false` if
|
||||
* a deep link should be rejected.
|
||||
*
|
||||
* @See DeepLinkIntentProcessor.verifier
|
||||
*/
|
||||
override fun verifyDeepLink(deepLink: Uri): Boolean {
|
||||
// We compare the local Leanplum device ID against the "uid" query parameter and only
|
||||
// accept deep links where both values match.
|
||||
val uid = deepLink.getQueryParameter("uid")
|
||||
return uid == Leanplum.getDeviceId()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
if (application.settings().isMarketingTelemetryEnabled) return
|
||||
// As written in LeanPlum SDK documentation, "This prevents Leanplum from communicating with the server."
|
||||
// as this "isTestMode" flag is checked before LeanPlum SDK does anything.
|
||||
// Also has the benefit effect of blocking the display of already downloaded messages.
|
||||
// The reverse of this - setIsTestModeEnabled(false) must be called before trying to init
|
||||
// LP in the same session.
|
||||
Leanplum.setIsTestModeEnabled(true)
|
||||
|
||||
// This is just to allow restarting LP and it's functionality in the same app session
|
||||
// as LP stores it's state internally and check against it
|
||||
LeanplumInternal.setCalledStart(false)
|
||||
LeanplumInternal.setHasStarted(false)
|
||||
leanplumJob?.cancel()
|
||||
}
|
||||
|
||||
override fun track(event: Event) {
|
||||
val leanplumExtras = event.extras
|
||||
?.map { (key, value) -> key.toString() to value }
|
||||
?.toMap()
|
||||
|
||||
event.name?.also {
|
||||
Leanplum.track(it, leanplumExtras)
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldTrack(event: Event): Boolean {
|
||||
return application.settings().isTelemetryEnabled &&
|
||||
token.type != Token.Type.Invalid && !event.name.isNullOrEmpty()
|
||||
}
|
||||
|
||||
private fun isLeanplumEnabled(locale: String): Boolean {
|
||||
return LEANPLUM_ENABLED_LOCALES.contains(locale)
|
||||
}
|
||||
|
||||
private val Locale.iso3LanguageOrNull: String?
|
||||
get() =
|
||||
try {
|
||||
this.isO3Language
|
||||
} catch (_: MissingResourceException) {
|
||||
null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val LOGTAG = "LeanplumMetricsService"
|
||||
|
||||
private val LeanplumId: String
|
||||
// Debug builds have a null (nullable) LEANPLUM_ID
|
||||
get() = BuildConfig.LEANPLUM_ID.orEmpty()
|
||||
private val LeanplumToken: String
|
||||
// Debug builds have a null (nullable) LEANPLUM_TOKEN
|
||||
get() = BuildConfig.LEANPLUM_TOKEN.orEmpty()
|
||||
|
||||
// Leanplum needs to be enabled for the following locales.
|
||||
// Irrespective of the actual device location.
|
||||
private val LEANPLUM_ENABLED_LOCALES = setOf(
|
||||
"eng", // English
|
||||
"zho", // Chinese
|
||||
"deu", // German
|
||||
"fra", // French
|
||||
"ita", // Italian
|
||||
"ind", // Indonesian
|
||||
"por", // Portuguese
|
||||
"spa", // Spanish; Castilian
|
||||
"pol", // Polish
|
||||
"rus", // Russian
|
||||
"hin", // Hindi
|
||||
"per", // Persian
|
||||
"fas", // Persian
|
||||
"ara", // Arabic
|
||||
"jpn" // Japanese
|
||||
)
|
||||
|
||||
private const val PREFERENCE_NAME = "LEANPLUM_PREFERENCES"
|
||||
private const val DEVICE_ID_KEY = "LP_DEVICE_ID"
|
||||
}
|
||||
}
|
@ -1,153 +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.home.intent
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.provider.Settings
|
||||
import androidx.navigation.NavController
|
||||
import mozilla.components.concept.engine.EngineSession
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import org.mozilla.fenix.BrowserDirection
|
||||
import org.mozilla.fenix.BuildConfig
|
||||
import org.mozilla.fenix.GlobalDirections
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||
import org.mozilla.fenix.components.SearchWidgetCreator
|
||||
import org.mozilla.fenix.ext.alreadyOnDestination
|
||||
|
||||
/**
|
||||
* Deep links in the form of `fenix://host` open different parts of the app.
|
||||
*
|
||||
* @param verifier [DeepLinkVerifier] that will be used to verify deep links before handling them.
|
||||
*/
|
||||
class DeepLinkIntentProcessor(
|
||||
private val activity: HomeActivity,
|
||||
private val verifier: DeepLinkVerifier
|
||||
) : HomeIntentProcessor {
|
||||
private val logger = Logger("DeepLinkIntentProcessor")
|
||||
|
||||
override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
|
||||
val scheme = intent.scheme?.equals(BuildConfig.DEEP_LINK_SCHEME, ignoreCase = true) ?: return false
|
||||
return if (scheme) {
|
||||
intent.data?.let { handleDeepLink(it, navController) }
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun handleDeepLink(deepLink: Uri, navController: NavController) {
|
||||
if (!verifier.verifyDeepLink(deepLink)) {
|
||||
logger.warn("Invalid deep link: $deepLink")
|
||||
return
|
||||
}
|
||||
|
||||
handleDeepLinkSideEffects(deepLink)
|
||||
|
||||
val globalDirections = when (deepLink.host) {
|
||||
"home", "enable_private_browsing" -> GlobalDirections.Home
|
||||
"urls_bookmarks" -> GlobalDirections.Bookmarks
|
||||
"urls_history" -> GlobalDirections.History
|
||||
"settings" -> GlobalDirections.Settings
|
||||
"turn_on_sync" -> GlobalDirections.Sync
|
||||
"settings_search_engine" -> GlobalDirections.SearchEngine
|
||||
"settings_accessibility" -> GlobalDirections.Accessibility
|
||||
"settings_delete_browsing_data" -> GlobalDirections.DeleteData
|
||||
"settings_addon_manager" -> GlobalDirections.SettingsAddonManager
|
||||
"settings_logins" -> GlobalDirections.SettingsLogins
|
||||
"settings_tracking_protection" -> GlobalDirections.SettingsTrackingProtection
|
||||
// We'd like to highlight views within the fragment
|
||||
// https://github.com/mozilla-mobile/fenix/issues/11856
|
||||
// The current version of UI has these features in more complex screens.
|
||||
"settings_privacy" -> GlobalDirections.Settings
|
||||
"home_collections" -> GlobalDirections.Home
|
||||
|
||||
else -> return
|
||||
}
|
||||
|
||||
if (!navController.alreadyOnDestination(globalDirections.destinationId)) {
|
||||
navController.navigate(globalDirections.navDirections)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle links that require more than just simple navigation.
|
||||
*/
|
||||
private fun handleDeepLinkSideEffects(deepLink: Uri) {
|
||||
when (deepLink.host) {
|
||||
"enable_private_browsing" -> {
|
||||
activity.browsingModeManager.mode = BrowsingMode.Private
|
||||
}
|
||||
"make_default_browser" -> {
|
||||
if (SDK_INT >= Build.VERSION_CODES.N) {
|
||||
val settingsIntent = Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
|
||||
activity.startActivity(settingsIntent)
|
||||
}
|
||||
}
|
||||
"open" -> {
|
||||
val url = deepLink.getQueryParameter("url")
|
||||
if (url == null || !url.startsWith("https://")) {
|
||||
logger.info("Not opening deep link: $url")
|
||||
return
|
||||
}
|
||||
|
||||
activity.openToBrowserAndLoad(
|
||||
url,
|
||||
newTab = true,
|
||||
from = BrowserDirection.FromGlobal,
|
||||
flags = EngineSession.LoadUrlFlags.external()
|
||||
)
|
||||
}
|
||||
"settings_notifications" -> {
|
||||
val intent = notificationSettings(activity)
|
||||
activity.startActivity(intent)
|
||||
}
|
||||
"install_search_widget" -> {
|
||||
if (SDK_INT >= Build.VERSION_CODES.O) {
|
||||
SearchWidgetCreator.createSearchWidget(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notificationSettings(context: Context, channel: String? = null) =
|
||||
Intent().apply {
|
||||
when {
|
||||
SDK_INT >= Build.VERSION_CODES.O -> {
|
||||
action = channel?.let {
|
||||
putExtra(Settings.EXTRA_CHANNEL_ID, it)
|
||||
Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS
|
||||
} ?: Settings.ACTION_APP_NOTIFICATION_SETTINGS
|
||||
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
|
||||
}
|
||||
SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
|
||||
action = "android.settings.APP_NOTIFICATION_SETTINGS"
|
||||
putExtra("app_package", context.packageName)
|
||||
putExtra("app_uid", context.applicationInfo.uid)
|
||||
}
|
||||
else -> {
|
||||
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
addCategory(Intent.CATEGORY_DEFAULT)
|
||||
data = Uri.parse("package:" + context.packageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for a class that verifies deep links before they get handled.
|
||||
*/
|
||||
interface DeepLinkVerifier {
|
||||
/**
|
||||
* Verifies the given deep link and returns `true` for verified deep links or `false` for
|
||||
* rejected deep links.
|
||||
*/
|
||||
fun verifyDeepLink(deepLink: Uri): Boolean
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/* 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.sessioncontrol.viewholders.onboarding
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.experiment_default_browser.view.*
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.increaseTapArea
|
||||
import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor
|
||||
|
||||
class ExperimentDefaultBrowserCardViewHolder(
|
||||
view: View,
|
||||
private val interactor: SessionControlInteractor
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
init {
|
||||
view.set_default_browser.setOnClickListener {
|
||||
interactor.onSetDefaultBrowserClicked()
|
||||
}
|
||||
|
||||
view.close.apply {
|
||||
increaseTapArea(CLOSE_BUTTON_EXTRA_DPS)
|
||||
setOnClickListener {
|
||||
interactor.onCloseExperimentCardClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal const val LAYOUT_ID = R.layout.experiment_default_browser
|
||||
private const val CLOSE_BUTTON_EXTRA_DPS = 38
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue