For #23046 - Align detekt config between Fenix and Android Components

Co-authored-by: Gabriel Luong <gabriel.luong@gmail.com>
jhugman/fix-breaking-changes-for-app-services-93.0.0
Brais Gabín 11 months ago committed by mergify[bot]
parent 57238fa5ca
commit 1f633edd7d
  1. 27
      .experimenter.json
  2. 19
      app/src/main/java/org/mozilla/fenix/FenixApplication.kt
  3. 4
      app/src/main/java/org/mozilla/fenix/FenixLogSink.kt
  4. 8
      app/src/main/java/org/mozilla/fenix/collections/CollectionCreationBottomBarView.kt
  5. 17
      app/src/main/java/org/mozilla/fenix/collections/CollectionCreationView.kt
  6. 4
      app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt
  7. 7
      app/src/main/java/org/mozilla/fenix/components/metrics/MetricsUtils.kt
  8. 16
      app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt
  9. 2
      app/src/main/java/org/mozilla/fenix/compose/ThumbnailCard.kt
  10. 18
      app/src/main/java/org/mozilla/fenix/downloads/DynamicDownloadDialogBehavior.kt
  11. 13
      app/src/main/java/org/mozilla/fenix/home/pocket/PocketStoriesComposables.kt
  12. 7
      app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt
  13. 7
      app/src/main/java/org/mozilla/fenix/home/topsites/TopSitesPagerAdapter.kt
  14. 7
      app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt
  15. 7
      app/src/main/java/org/mozilla/fenix/library/history/viewholders/HistoryListItemViewHolder.kt
  16. 6
      app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt
  17. 8
      app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt
  18. 8
      app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt
  19. 14
      app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt
  20. 2
      app/src/main/java/org/mozilla/fenix/settings/logins/controller/SavedLoginsStorageController.kt
  21. 6
      app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt
  22. 5
      app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsController.kt
  23. 4
      app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFragment.kt
  24. 8
      app/src/main/java/org/mozilla/fenix/shortcut/NewTabShortcutIntentProcessor.kt
  25. 1
      app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt
  26. 3
      app/src/main/java/org/mozilla/fenix/tabstray/browser/SelectionBannerBinding.kt
  27. 1
      app/src/main/java/org/mozilla/fenix/utils/Settings.kt
  28. 7
      app/src/main/java/org/mozilla/gecko/search/SearchWidgetProvider.kt
  29. 52
      app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt
  30. 19
      app/src/test/java/org/mozilla/fenix/settings/quicksettings/DefaultQuickSettingsControllerTest.kt
  31. 26
      build.gradle
  32. 2
      buildSrc/src/main/java/Dependencies.kt
  33. 429
      config/detekt.yml
  34. 1322
      detekt-baseline.xml

@ -21,6 +21,33 @@
}
}
},
"messaging": {
"description": "Configuration for the messaging system.\n\nIn practice this is a set of growable lookup tables for the\nmessage controller to piece together.\n",
"hasExposure": true,
"exposureDescription": "",
"variables": {
"actions": {
"type": "json",
"description": "A growable map of action URLs."
},
"message-under-experiment": {
"type": "string",
"description": "Id or prefix of the message under experiment."
},
"messages": {
"type": "json",
"description": "A growable collection of messages"
},
"styles": {
"type": "json",
"description": "A map of styles to configure message appearance.\n"
},
"triggers": {
"type": "json",
"description": "A collection of out the box trigger expressions. Each entry maps to a valid JEXL expression.\n"
}
}
},
"nimbus-validation": {
"description": "A feature that does not correspond to an application feature suitable for showing that Nimbus is working. This should never be used in production.",
"hasExposure": true,

@ -342,16 +342,15 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
// To re-enable this, we need to do so in a way that won't interfere with any startup operations
// which acquire reserved+ sqlite lock. Currently, Fennec migrations need to write to storage
// on startup, and since they run in a background service we can't simply order these operations.
@OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
private fun runStorageMaintenance() {
GlobalScope.launch(Dispatchers.IO) {
// Bookmarks and history storage sit on top of the same db file so we only need to
// run maintenance on one - arbitrarily using bookmarks.
components.core.bookmarksStorage.runMaintenance()
}
settings().lastPlacesStorageMaintenance = System.currentTimeMillis()
}
// @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
// private fun runStorageMaintenance() {
// GlobalScope.launch(Dispatchers.IO) {
// // Bookmarks and history storage sit on top of the same db file so we only need to
// // run maintenance on one - arbitrarily using bookmarks.
// // components.core.bookmarksStorage.runMaintenance()
// }
// settings().lastPlacesStorageMaintenance = System.currentTimeMillis()
// }
protected open fun setupLeakCanary() {
// no-op, LeakCanary is disabled by default

@ -23,8 +23,10 @@ class FenixLogSink(private val logsDebug: Boolean = true) : LogSink {
throwable: Throwable?,
message: String?
) {
if (priority == Log.Priority.DEBUG && !logsDebug)
if (priority == Log.Priority.DEBUG && !logsDebug) {
return
}
androidLogSink.log(priority, tag, throwable, message)
}
}

@ -53,9 +53,11 @@ class CollectionCreationBottomBarView(
context.getString(R.string.create_collection_save_to_collection_empty)
} else {
context.getString(
if (state.selectedTabs.size == 1)
R.string.create_collection_save_to_collection_tab_selected else
R.string.create_collection_save_to_collection_tabs_selected,
if (state.selectedTabs.size == 1) {
R.string.create_collection_save_to_collection_tab_selected
} else {
R.string.create_collection_save_to_collection_tabs_selected
},
state.selectedTabs.size
)
}

@ -122,12 +122,19 @@ class CollectionCreationView(
binding.selectAllButton.apply {
val allSelected = state.selectedTabs.size == state.tabs.size
text =
if (allSelected) context.getString(R.string.create_collection_deselect_all)
else context.getString(R.string.create_collection_select_all)
text = if (allSelected) {
context.getString(R.string.create_collection_deselect_all)
} else {
context.getString(R.string.create_collection_select_all)
}
setOnClickListener {
if (allSelected) interactor.deselectAllTapped()
else interactor.selectAllTapped()
if (allSelected) {
interactor.deselectAllTapped()
} else {
interactor.selectAllTapped()
}
}
}

@ -9,14 +9,12 @@ import android.content.SharedPreferences
import android.os.StrictMode
import androidx.annotation.GuardedBy
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.Dispatchers
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.lib.crash.CrashReporter
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.perf.StrictModeManager
import kotlin.coroutines.CoroutineContext
/**
* Miscellaneous FxA-related abnormalities.
@ -51,13 +49,11 @@ internal abstract class AbnormalFxaEvent : Exception() {
* See [AbnormalFxaEvent] for types of abnormal events this class detects.
*
* @param crashReporter An instance of [CrashReporter] used for reporting detected abnormalities.
* @param coroutineContext A [CoroutineContext] used for executing async tasks. Defaults to [Dispatchers.IO].
*/
class AccountAbnormalities(
context: Context,
private val crashReporter: CrashReporter,
strictMode: StrictModeManager,
private val coroutineContext: CoroutineContext = Dispatchers.IO
) : AccountObserver {
companion object {
private const val PREF_FXA_ABNORMALITIES = "fxa_abnormalities"

@ -33,8 +33,11 @@ object MetricsUtils {
val isCustom = engine.type == SearchEngine.Type.CUSTOM
val engineSource =
if (isShortcut) Event.PerformedSearch.EngineSource.Shortcut(engine, isCustom)
else Event.PerformedSearch.EngineSource.Default(engine, isCustom)
if (isShortcut) {
Event.PerformedSearch.EngineSource.Shortcut(engine, isCustom)
} else {
Event.PerformedSearch.EngineSource.Default(engine, isCustom)
}
return when (searchAccessPoint) {
SearchAccessPoint.SUGGESTION -> Event.PerformedSearch(

@ -302,12 +302,16 @@ open class DefaultToolbarMenu(
val settingsItem = BrowserMenuHighlightableItem(
label = context.getString(R.string.browser_menu_settings),
startImageResource = R.drawable.mozac_ic_settings,
iconTintColorResource = if (hasAccountProblem)
ThemeManager.resolveAttribute(R.attr.syncDisconnected, context) else
primaryTextColor(),
textColorResource = if (hasAccountProblem)
ThemeManager.resolveAttribute(R.attr.textPrimary, context) else
primaryTextColor(),
iconTintColorResource = if (hasAccountProblem) {
ThemeManager.resolveAttribute(R.attr.syncDisconnected, context)
} else {
primaryTextColor()
},
textColorResource = if (hasAccountProblem) {
ThemeManager.resolveAttribute(R.attr.textPrimary, context)
} else {
primaryTextColor()
},
highlight = BrowserMenuHighlight.HighPriority(
endImageResource = R.drawable.ic_sync_disconnected,
backgroundTint = context.getColorFromAttr(R.attr.syncDisconnectedBackground),

@ -119,7 +119,7 @@ private fun ThumbnailImage(
@Preview
@Composable
fun ThumbnailCardPreview() {
private fun ThumbnailCardPreview() {
ThumbnailCard(
url = "https://mozilla.com",
key = "123",

@ -100,13 +100,17 @@ class DynamicDownloadDialogBehavior<V : View>(
) {
if (shouldSnapAfterScroll || type == ViewCompat.TYPE_NON_TOUCH) {
if (expanded) {
if (child.translationY >= bottomToolbarHeight / 2)
if (child.translationY >= bottomToolbarHeight / 2) {
animateSnap(child, SnapDirection.DOWN)
else animateSnap(child, SnapDirection.UP)
} else {
animateSnap(child, SnapDirection.UP)
}
} else {
if (child.translationY < (bottomToolbarHeight + child.height.toFloat() / 2))
if (child.translationY < (bottomToolbarHeight + child.height.toFloat() / 2)) {
animateSnap(child, SnapDirection.UP)
else animateSnap(child, SnapDirection.DOWN)
} else {
animateSnap(child, SnapDirection.DOWN)
}
}
}
}
@ -151,7 +155,11 @@ class DynamicDownloadDialogBehavior<V : View>(
addUpdateListener { child.translationY = it.animatedValue as Float }
setFloatValues(
child.translationY,
if (direction == SnapDirection.UP) 0f else child.height.toFloat() + bottomToolbarHeight
if (direction == SnapDirection.UP) {
0f
} else {
child.height.toFloat() + bottomToolbarHeight
}
)
start()
}

@ -49,7 +49,6 @@ import org.mozilla.fenix.compose.TabSubtitleWithInterdot
import org.mozilla.fenix.compose.TabTitle
import org.mozilla.fenix.theme.FirefoxTheme
import kotlin.math.roundToInt
import kotlin.random.Random
private const val URI_PARAM_UTM_KEY = "utm_source"
private const val POCKET_STORIES_UTM_VALUE = "pocket-newtab-android"
@ -274,17 +273,15 @@ private class PocketStoryProvider : PreviewParameterProvider<PocketRecommendedSt
internal fun getFakePocketStories(limit: Int = 1): List<PocketRecommendedStory> {
return mutableListOf<PocketRecommendedStory>().apply {
for (index in 0 until limit) {
val randomNumber = Random.nextInt(0, 10)
add(
PocketRecommendedStory(
title = "This is a ${"very ".repeat(randomNumber)} long title",
title = "This is a ${"very ".repeat(index)} long title",
publisher = "Publisher",
url = "https://story$randomNumber.com",
url = "https://story$index.com",
imageUrl = "",
timeToRead = randomNumber,
category = "Category #$randomNumber",
timesShown = randomNumber.toLong()
timeToRead = index,
category = "Category #$index",
timesShown = index.toLong()
)
)
}

@ -651,8 +651,11 @@ class DefaultSessionControlController(
override fun handleReportSessionMetrics(state: AppState) {
with(metrics) {
track(
if (state.recentTabs.isEmpty()) Event.RecentTabsSectionIsNotVisible
else Event.RecentTabsSectionIsVisible
if (state.recentTabs.isEmpty()) {
Event.RecentTabsSectionIsNotVisible
} else {
Event.RecentTabsSectionIsVisible
}
)
track(Event.RecentBookmarkCount(state.recentBookmarks.size))

@ -77,8 +77,11 @@ class TopSitesPagerAdapter(
@VisibleForTesting
internal fun getCurrentPageChanges(payload: TopSitePagerPayload, position: Int) =
payload.changed.filter { changedPair ->
if (position == 0) changedPair.first < TOP_SITES_PER_PAGE
else changedPair.first >= TOP_SITES_PER_PAGE
if (position == 0) {
changedPair.first < TOP_SITES_PER_PAGE
} else {
changedPair.first >= TOP_SITES_PER_PAGE
}
}
override fun onBindViewHolder(holder: TopSiteViewHolder, position: Int) {

@ -104,8 +104,11 @@ class HistoryView(
val numRecentTabs = recentlyClosedNav.context.components.core.store.state.closedTabs.size
recentlyClosedTabsDescription.text = String.format(
context.getString(
if (numRecentTabs == 1)
R.string.recently_closed_tab else R.string.recently_closed_tabs
if (numRecentTabs == 1) {
R.string.recently_closed_tab
} else {
R.string.recently_closed_tabs
}
),
numRecentTabs
)

@ -116,8 +116,11 @@ class HistoryListItemViewHolder(
val numRecentTabs = itemView.context.components.core.store.state.closedTabs.size
binding.recentlyClosedNavEmpty.recentlyClosedTabsDescription.text = String.format(
itemView.context.getString(
if (numRecentTabs == 1)
R.string.recently_closed_tab else R.string.recently_closed_tabs
if (numRecentTabs == 1) {
R.string.recently_closed_tab
} else {
R.string.recently_closed_tabs
}
),
numRecentTabs
)

@ -72,9 +72,11 @@ class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler {
scanMessage =
if (requireContext().settings().allowDomesticChinaFxaServer &&
org.mozilla.fenix.Config.channel.isMozillaOnline
)
) {
R.string.pair_instructions_2_cn
else R.string.pair_instructions_2
} else {
R.string.pair_instructions_2
}
),
owner = this,
view = view

@ -107,11 +107,11 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
accountSettingsStore = StoreProvider.get(this) {
AccountSettingsFragmentStore(
AccountSettingsFragmentState(
lastSyncedDate =
if (getLastSynced(requireContext()) == 0L)
lastSyncedDate = if (getLastSynced(requireContext()) == 0L) {
LastSyncTime.Never
else
LastSyncTime.Success(getLastSynced(requireContext())),
} else {
LastSyncTime.Success(getLastSynced(requireContext()))
},
deviceName = requireComponents.backgroundServices.defaultDeviceName(
requireContext()
)

@ -91,10 +91,10 @@ class TurnOnSyncFragment : Fragment(), AccountObserver {
override fun onResume() {
super.onResume()
if (pairWithEmailStarted ||
requireComponents.backgroundServices.accountManager.authenticatedAccount() != null
) {
findNavController().popBackStack()
return
}
@ -118,9 +118,11 @@ class TurnOnSyncFragment : Fragment(), AccountObserver {
binding.signInScanButton.setOnClickListener(paringClickListener)
binding.signInEmailButton.setOnClickListener(signInClickListener)
binding.signInInstructions.text = HtmlCompat.fromHtml(
if (requireContext().settings().allowDomesticChinaFxaServer && Config.channel.isMozillaOnline)
if (requireContext().settings().allowDomesticChinaFxaServer && Config.channel.isMozillaOnline) {
getString(R.string.sign_in_instructions_cn)
else getString(R.string.sign_in_instructions),
} else {
getString(R.string.sign_in_instructions)
},
HtmlCompat.FROM_HTML_MODE_LEGACY
)

@ -192,12 +192,14 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da
.setText(resources.getString(R.string.preferences_delete_browsing_data_snackbar))
.show()
if (popAfter) viewLifecycleOwner.lifecycleScope.launch(Main) {
findNavController().apply {
// If the user deletes all open tabs we need to make sure we remove
// the BrowserFragment from the backstack.
popBackStack(R.id.homeFragment, false)
navigate(DeleteBrowsingDataFragmentDirections.actionGlobalSettingsFragment())
if (popAfter) {
viewLifecycleOwner.lifecycleScope.launch(Main) {
findNavController().apply {
// If the user deletes all open tabs we need to make sure we remove
// the BrowserFragment from the backstack.
popBackStack(R.id.homeFragment, false)
navigate(DeleteBrowsingDataFragmentDirections.actionGlobalSettingsFragment())
}
}
}
}

@ -39,8 +39,6 @@ open class SavedLoginsStorageController(
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
private suspend fun getLogin(loginId: String): Login? = passwordsStorage.get(loginId)
fun delete(loginId: String) {
var deleteLoginJob: Deferred<Boolean>? = null
val deleteJob = lifecycleScope.launch(ioDispatcher) {

@ -89,9 +89,11 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat() {
requirePreference<Preference>(R.string.pref_key_save_logins_settings).apply {
summary = getString(
if (context.settings().shouldPromptToSaveLogins)
R.string.preferences_passwords_save_logins_ask_to_save else
if (context.settings().shouldPromptToSaveLogins) {
R.string.preferences_passwords_save_logins_ask_to_save
} else {
R.string.preferences_passwords_save_logins_never_save
}
)
setOnPreferenceClickListener {
navigateToSaveLoginSettingFragment()

@ -14,7 +14,6 @@ import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.permission.SitePermissions
import mozilla.components.feature.session.SessionUseCases.ReloadUrlUseCase
import mozilla.components.feature.tabs.TabsUseCases.AddNewTabUseCase
import mozilla.components.support.base.feature.OnNeedToRequestPermissions
import mozilla.components.support.ktx.kotlin.getOrigin
import mozilla.telemetry.glean.private.NoExtras
@ -95,11 +94,9 @@ interface QuickSettingsController {
* @param settings [Settings] application settings.
* @param permissionStorage [PermissionStorage] app state for website permissions exception.
* @param reload [ReloadUrlUseCase] callback allowing for reloading the current web page.
* @param addNewTab [AddNewTabUseCase] callback allowing for loading a URL in a new tab.
* @param requestRuntimePermissions [OnNeedToRequestPermissions] callback allowing for requesting
* specific Android runtime permissions.
* @param displayPermissions callback for when [WebsitePermissionsView] needs to be displayed.
* @param dismiss callback allowing to request this entire Fragment to be dismissed.
*/
@Suppress("TooManyFunctions")
class DefaultQuickSettingsController(
@ -115,11 +112,9 @@ class DefaultQuickSettingsController(
private val settings: Settings,
private val permissionStorage: PermissionStorage,
private val reload: ReloadUrlUseCase,
private val addNewTab: AddNewTabUseCase,
private val requestRuntimePermissions: OnNeedToRequestPermissions = { },
private val displayPermissions: () -> Unit,
private val engine: Engine = context.components.core.engine,
private val dismiss: () -> Unit
) : QuickSettingsController {
override fun handlePermissionsShown() {
displayPermissions()

@ -103,13 +103,11 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
settings = components.settings,
permissionStorage = components.core.permissionStorage,
reload = components.useCases.sessionUseCases.reload,
addNewTab = components.useCases.tabsUseCases.addTab,
requestRuntimePermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS)
tryToRequestPermissions = true
},
displayPermissions = ::showPermissionsView,
dismiss = ::dismiss
displayPermissions = ::showPermissionsView
)
interactor = QuickSettingsInteractor(quickSettingsController)

@ -14,14 +14,6 @@ import org.mozilla.fenix.home.intent.StartSearchIntentProcessor
class NewTabShortcutIntentProcessor : IntentProcessor {
/**
* Returns true if this intent processor will handle the intent.
*/
private fun matches(intent: Intent): Boolean {
val safeIntent = SafeIntent(intent)
return safeIntent.action == ACTION_OPEN_TAB || safeIntent.action == ACTION_OPEN_PRIVATE_TAB
}
/**
* Processes the given [Intent].
*

@ -322,7 +322,6 @@ class TabsTrayFragment : AppCompatDialogFragment() {
store = tabsTrayStore,
navInteractor = navigationInteractor,
tabsTrayInteractor = tabsTrayInteractor,
containerView = view,
backgroundView = tabsTrayBinding.topBar,
showOnSelectViews = VisibilityModifier(
tabsTrayMultiselectItemsBinding.collectMultiSelect,

@ -34,8 +34,6 @@ import org.mozilla.fenix.tabstray.ext.showWithTheme
* @property store The TabsTrayStore instance.
* @property navInteractor An instance of [NavigationInteractor] for navigating on menu clicks.
* @property tabsTrayInteractor An instance of [TabsTrayInteractor] for handling deletion.
* @property containerView The view in the layout that contains all the implicit multi-select
* views. NB: This parameter is a bit opaque and requires a larger layout refactor to correct.
* @property backgroundView The background view that we want to alter when changing [Mode].
* @property showOnSelectViews A variable list of views that will be made visible when in select mode.
* @property showOnNormalViews A variable list of views that will be made visible when in normal mode.
@ -48,7 +46,6 @@ class SelectionBannerBinding(
private val store: TabsTrayStore,
private val navInteractor: NavigationInteractor,
private val tabsTrayInteractor: TabsTrayInteractor,
private val containerView: View,
private val backgroundView: View,
private val showOnSelectViews: VisibilityModifier,
private val showOnNormalViews: VisibilityModifier

@ -70,7 +70,6 @@ class Settings(private val appContext: Context) : PreferencesHolder {
private const val ALLOWED_INT = 2
private const val CFR_COUNT_CONDITION_FOCUS_INSTALLED = 1
private const val CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED = 3
private const val APP_LAUNCHES_TO_SHOW_DEFAULT_BROWSER_CARD = 3
private const val INACTIVE_TAB_MINIMUM_TO_SHOW_AUTO_CLOSE_DIALOG = 20
const val FOUR_HOURS_MS = 60 * 60 * 4 * 1000L

@ -224,8 +224,11 @@ class SearchWidgetProvider : AppWidgetProvider() {
SearchWidgetProviderSize.LARGE -> R.layout.search_widget_large
SearchWidgetProviderSize.MEDIUM -> R.layout.search_widget_medium
SearchWidgetProviderSize.SMALL -> {
if (showMic) R.layout.search_widget_small
else R.layout.search_widget_small_no_mic
if (showMic) {
R.layout.search_widget_small
} else {
R.layout.search_widget_small_no_mic
}
}
SearchWidgetProviderSize.EXTRA_SMALL_V2 -> R.layout.search_widget_extra_small_v2
SearchWidgetProviderSize.EXTRA_SMALL_V1 -> R.layout.search_widget_extra_small_v1

@ -19,7 +19,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.helpers.perf.TestStrictModeManager
import kotlin.coroutines.CoroutineContext
@RunWith(FenixRobolectricTestRunner::class)
class AccountAbnormalitiesTest {
@ -28,7 +27,11 @@ class AccountAbnormalitiesTest {
val crashReporter: CrashReporter = mockk()
// no account present
val accountAbnormalities = newAccountAbnormalities(crashReporter)
val accountAbnormalities = AccountAbnormalities(
testContext,
crashReporter,
TestStrictModeManager() as StrictModeManager
)
try {
accountAbnormalities.userRequestedLogout()
@ -54,7 +57,11 @@ class AccountAbnormalitiesTest {
fun `LogoutWithoutAuth detected`() = runBlocking {
val crashReporter: CrashReporter = mockk(relaxed = true)
val accountAbnormalities = newAccountAbnormalities(crashReporter, this.coroutineContext)
val accountAbnormalities = AccountAbnormalities(
testContext,
crashReporter,
TestStrictModeManager() as StrictModeManager
)
accountAbnormalities.onReady(mockk(relaxed = true))
// Logout action must be preceded by auth.
@ -66,7 +73,11 @@ class AccountAbnormalitiesTest {
fun `OverlappingFxaLogoutRequest detected`() = runBlocking {
val crashReporter: CrashReporter = mockk(relaxed = true)
val accountAbnormalities = newAccountAbnormalities(crashReporter, this.coroutineContext)
val accountAbnormalities = AccountAbnormalities(
testContext,
crashReporter,
TestStrictModeManager() as StrictModeManager
)
accountAbnormalities.onReady(mockk(relaxed = true))
accountAbnormalities.onAuthenticated(mockk(), mockk())
@ -83,7 +94,11 @@ class AccountAbnormalitiesTest {
fun `callback logout abnormalities detected`() = runBlocking {
val crashReporter: CrashReporter = mockk(relaxed = true)
val accountAbnormalities = newAccountAbnormalities(crashReporter, this.coroutineContext)
val accountAbnormalities = AccountAbnormalities(
testContext,
crashReporter,
TestStrictModeManager() as StrictModeManager
)
accountAbnormalities.onReady(mockk(relaxed = true))
// User didn't request this logout.
@ -96,7 +111,11 @@ class AccountAbnormalitiesTest {
val crashReporter: CrashReporter = mockk(relaxed = true)
val accountManager: FxaAccountManager = mockk(relaxed = true)
val accountAbnormalities = newAccountAbnormalities(crashReporter, this.coroutineContext)
val accountAbnormalities = AccountAbnormalities(
testContext,
crashReporter,
TestStrictModeManager() as StrictModeManager
)
accountAbnormalities.onReady(null)
accountAbnormalities.onAuthenticated(mockk(), mockk())
@ -104,7 +123,11 @@ class AccountAbnormalitiesTest {
every { accountManager.authenticatedAccount() } returns null
// Pretend we restart, and instantiate a new middleware instance.
val accountAbnormalities2 = newAccountAbnormalities(crashReporter, this.coroutineContext)
val accountAbnormalities2 = AccountAbnormalities(
testContext,
crashReporter,
TestStrictModeManager() as StrictModeManager
)
// mock accountManager doesn't have an account, but we expect it to have one since we
// were authenticated before our "restart".
accountAbnormalities2.onReady(null)
@ -116,7 +139,11 @@ class AccountAbnormalitiesTest {
fun `logout happy case`() = runBlocking {
val crashReporter: CrashReporter = mockk()
val accountAbnormalities = newAccountAbnormalities(crashReporter, coroutineContext)
val accountAbnormalities = AccountAbnormalities(
testContext,
crashReporter,
TestStrictModeManager() as StrictModeManager
)
accountAbnormalities.onReady(mockk(relaxed = true))
// We saw an auth event, then user requested a logout.
@ -130,13 +157,4 @@ class AccountAbnormalitiesTest {
crashReporter.submitCaughtException(any<T>())
}
}
private fun newAccountAbnormalities(
crashReporter: CrashReporter,
coroutineContext: CoroutineContext? = null
): AccountAbnormalities = if (coroutineContext != null) {
AccountAbnormalities(testContext, crashReporter, TestStrictModeManager() as StrictModeManager, coroutineContext)
} else {
AccountAbnormalities(testContext, crashReporter, TestStrictModeManager() as StrictModeManager)
}
}

@ -26,7 +26,6 @@ import mozilla.components.concept.engine.permission.SitePermissions
import mozilla.components.concept.engine.permission.SitePermissions.Status.NO_DECISION
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.feature.session.TrackingProtectionUseCases
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.service.glean.testing.GleanTestRule
import mozilla.components.support.test.mock
import mozilla.components.support.test.robolectric.testContext
@ -81,9 +80,6 @@ class DefaultQuickSettingsControllerTest {
@MockK(relaxed = true)
private lateinit var reload: SessionUseCases.ReloadUrlUseCase
@MockK(relaxed = true)
private lateinit var addNewTab: TabsUseCases.AddNewTabUseCase
@MockK(relaxed = true)
private lateinit var requestPermissions: (Array<String>) -> Unit
@ -112,11 +108,9 @@ class DefaultQuickSettingsControllerTest {
settings = appSettings,
permissionStorage = permissionStorage,
reload = reload,
addNewTab = addNewTab,
requestRuntimePermissions = requestPermissions,
engine = engine,
displayPermissions = {},
dismiss = {}
displayPermissions = {}
)
)
}
@ -192,10 +186,8 @@ class DefaultQuickSettingsControllerTest {
settings = appSettings,
permissionStorage = permissionStorage,
reload = reload,
addNewTab = addNewTab,
requestRuntimePermissions = requestPermissions,
displayPermissions = {},
dismiss = {}
displayPermissions = {}
)
every { websitePermission.phoneFeature } returns PhoneFeature.CAMERA
@ -416,8 +408,7 @@ class DefaultQuickSettingsControllerTest {
private fun createController(
requestPermissions: (Array<String>) -> Unit = { _ -> },
displayPermissions: () -> Unit = { },
dismiss: () -> Unit = { }
displayPermissions: () -> Unit = {}
): DefaultQuickSettingsController {
return spyk(
DefaultQuickSettingsController(
@ -431,10 +422,8 @@ class DefaultQuickSettingsControllerTest {
settings = appSettings,
permissionStorage = permissionStorage,
reload = reload,
addNewTab = addNewTab,
requestRuntimePermissions = requestPermissions,
displayPermissions = displayPermissions,
dismiss = dismiss
displayPermissions = displayPermissions
)
)
}

@ -78,7 +78,7 @@ buildscript {
}
plugins {
id("io.gitlab.arturbosch.detekt").version("1.17.1")
id("io.gitlab.arturbosch.detekt").version("1.19.0")
}
allprojects {
@ -154,8 +154,6 @@ tasks.register('clean', Delete) {
}
detekt {
// The version number is duplicated, please refer to plugins block for more details
version = "1.17.1"
input = files("$projectDir/app/src")
config = files("$projectDir/config/detekt.yml")
@ -167,9 +165,31 @@ detekt {
xml {
enabled = false
}
txt {
enabled = false
}
}
}
tasks.withType(io.gitlab.arturbosch.detekt.Detekt).configureEach() {
autoCorrect = true
exclude "**/test/**"
exclude "**/androidTest/**"
exclude "**/build/**"
exclude "**/resources/**"
exclude "**/tmp/**"
}
// Apply same path exclusions as for the main task
tasks.withType(io.gitlab.arturbosch.detekt.DetektCreateBaselineTask).configureEach() {
exclude "**/test/**"
exclude "**/androidTest/**"
exclude "**/build/**"
exclude "**/resources/**"
exclude "**/tmp/**"
}
configurations {
ktlint
}

@ -17,7 +17,7 @@ object Versions {
const val sentry = "5.6.2"
const val leakcanary = "2.8.1"
const val osslicenses_plugin = "0.10.4"
const val detekt = "1.17.1"
const val detekt = "1.19.0"
const val jna = "5.6.0"
const val androidx_activity_compose = "1.4.0"

@ -1,5 +1,6 @@
build:
maxIssues: 0
excludeCorrectable: false
weights:
# complexity: 2
# LongParameterList: 1
@ -9,11 +10,19 @@ build:
processors:
active: true
exclude:
# - 'DetektProgressListener'
# - 'KtFileCountProcessor'
# - 'PackageCountProcessor'
# - 'ClassCountProcessor'
# - 'FunctionCountProcessor'
# - 'PropertyCountProcessor'
# - 'ClassCountProcessor'
# - 'PackageCountProcessor'
# - 'KtFileCountProcessor'
# - 'ProjectComplexityProcessor'
# - 'ProjectCognitiveComplexityProcessor'
# - 'ProjectLLOCProcessor'
# - 'ProjectCLOCProcessor'
# - 'ProjectLOCProcessor'
# - 'ProjectSLOCProcessor'
# - 'LicenseHeaderLoaderExtension'
console-reports:
active: true
@ -22,30 +31,37 @@ console-reports:
# - 'ComplexityReport'
# - 'NotificationReport'
# - 'FindingsReport'
# - 'BuildFailureReport'
# - 'HtmlOutputReport'
- 'PlainOutputReport'
- 'XmlOutputReport'
# - 'FileBasedFindingsReport'
- 'LiteFindingsReport'
comments:
active: true
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
AbsentOrWrongFileLicense:
active: true
active: false
licenseTemplateFile: 'license.template'
licenseTemplateIsRegex: false
CommentOverPrivateFunction:
active: false
CommentOverPrivateProperty:
active: false
DeprecatedBlockTag:
active: false
EndOfSentenceFormat:
active: false
endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!]$)
UndocumentedPublicClass:
endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
OutdatedDocumentation:
active: false
matchTypeParameters: true
matchDeclarationsOrder: true
UndocumentedPublicClass:
active: true
searchInNestedClass: true
searchInInnerClass: true
searchInInnerObject: true
searchInInnerInterface: true
UndocumentedPublicFunction:
active: true
UndocumentedPublicProperty:
active: false
complexity:
@ -57,52 +73,83 @@ complexity:
active: false
threshold: 10
includeStaticDeclarations: false
includePrivateDeclarations: false
ComplexMethod:
active: true
threshold: 15
threshold: 18
ignoreSingleWhenExpression: true
ignoreSimpleWhenEntries: false
ignoreNestingFunctions: false
nestingFunctions:
- 'also'
- 'apply'
- 'forEach'
- 'isNotNull'
- 'ifNull'
- 'let'
- 'run'
- 'use'
- 'with'
LabeledExpression:
active: false
ignoredLabels: []
LargeClass:
active: true
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
# Had to increase the threshold as RC13 started counting lines of code
# https://github.com/mozilla-mobile/fenix/issues/4861
threshold: 200
threshold: 600
LongMethod:
active: true
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
# Had to increase the threshold as RC13 started counting lines of code
# https://github.com/mozilla-mobile/fenix/issues/4861
threshold: 75
LongParameterList:
active: true
excludes: "**/*Controller.kt, **/*Integration.kt"
functionThreshold: 6
constructorThreshold: 7
ignoreDefaultParameters: false
ignoreDefaultParameters: true
ignoreDataClasses: true
ignoreAnnotatedParameter: []
MethodOverloading:
active: false
threshold: 6
NamedArguments:
active: false
threshold: 3
NestedBlockDepth:
active: true
threshold: 4
ReplaceSafeCallChainWithRun:
active: false
StringLiteralDuplication:
active: false
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
threshold: 3
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: true
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
thresholdInFiles: 11
thresholdInClasses: 11
thresholdInInterfaces: 11
thresholdInObjects: 11
thresholdInFiles: 26
thresholdInClasses: 26
thresholdInInterfaces: 26
thresholdInObjects: 26
thresholdInEnums: 11
ignoreDeprecated: false
ignorePrivate: false
ignoreOverridden: false
coroutines:
active: true
GlobalCoroutineUsage:
active: false
InjectDispatcher:
active: false
dispatcherNames:
- 'IO'
- 'Default'
- 'Unconfined'
RedundantSuspendModifier:
active: false
SleepInsteadOfDelay:
active: false
SuspendFunWithFlowReturnType:
active: false
mozilla-detekt-rules:
active: true
@ -111,15 +158,13 @@ mozilla-detekt-rules:
# BuildConfig.Debug: This property tests whether the application was built
# with the debuggable flag or not. Use a check for different build variants,
# instead.
bannedProperties: "BuildConfig.DEBUG"
bannedProperties: 'BuildConfig.DEBUG'
MozillaStrictModeSuppression:
active: true
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
MozillaCorrectUnitTestRunner:
active: true
MozillaRunBlockingCheck:
active: true
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
MozillaUseLazyMonitored:
active: true
@ -127,7 +172,7 @@ empty-blocks:
active: true
EmptyCatchBlock:
active: true
allowedExceptionNameRegex: "^(ignore|expected).*"
allowedExceptionNameRegex: '_|(ignore|expected).*'
EmptyClassBlock:
active: true
EmptyDefaultConstructor:
@ -142,7 +187,7 @@ empty-blocks:
active: true
EmptyFunctionBlock:
active: true
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
ignoreOverridden: false
EmptyIfBlock:
active: true
EmptyInitBlock:
@ -151,6 +196,8 @@ empty-blocks:
active: true
EmptySecondaryConstructor:
active: true
EmptyTryBlock:
active: true
EmptyWhenBlock:
active: true
EmptyWhileBlock:
@ -160,61 +207,90 @@ exceptions:
active: true
ExceptionRaisedInUnexpectedLocation:
active: false
methodNames: 'toString,hashCode,equals,finalize'
methodNames:
- 'equals'
- 'finalize'
- 'hashCode'
- 'toString'
InstanceOfCheckForException:
active: false
NotImplementedDeclaration:
active: false
PrintStackTrace:
ObjectExtendsThrowable:
active: false
PrintStackTrace:
active: true
RethrowCaughtException:
active: false
ReturnFromFinally:
active: false
active: true
ignoreLabeled: false
SwallowedException:
active: false
ignoredExceptionTypes:
- 'InterruptedException'
- 'MalformedURLException'
- 'NumberFormatException'
- 'ParseException'
allowedExceptionNameRegex: '_|(ignore|expected).*'
ThrowingExceptionFromFinally:
active: false
active: true
ThrowingExceptionInMain:
active: false
ThrowingExceptionsWithoutMessageOrCause:
active: false
exceptions: 'IllegalArgumentException,IllegalStateException,IOException'
exceptions:
- 'ArrayIndexOutOfBoundsException'
- 'Exception'
- 'IllegalArgumentException'
- 'IllegalMonitorStateException'
- 'IllegalStateException'
- 'IndexOutOfBoundsException'
- 'NullPointerException'
- 'RuntimeException'
- 'Throwable'
ThrowingNewInstanceOfSameException:
active: false
active: true
TooGenericExceptionCaught:
active: true
exceptionNames:
- ArrayIndexOutOfBoundsException
- Error
- Exception
- IllegalMonitorStateException
- NullPointerException
- IndexOutOfBoundsException
- RuntimeException
- Throwable
- 'ArrayIndexOutOfBoundsException'
- 'Error'
- 'Exception'
- 'IllegalMonitorStateException'
- 'IndexOutOfBoundsException'
- 'NullPointerException'
- 'RuntimeException'
- 'Throwable'
allowedExceptionNameRegex: '_|(ignore|expected).*'
TooGenericExceptionThrown:
active: true
exceptionNames:
- Error
- Exception
- Throwable
- RuntimeException
formatting:
autoCorrect: true
- 'Error'
- 'Exception'
- 'RuntimeException'
- 'Throwable'
naming:
active: true
BooleanPropertyNaming:
active: false
allowedPattern: '^(is|has|are)'
ClassNaming:
active: true
classPattern: '[A-Z$][a-zA-Z0-9$]*'
classPattern: '[A-Z][a-zA-Z0-9]*'
ConstructorParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
EnumNaming:
active: true
enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*'
enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
forbiddenName: ''
forbiddenName: []
FunctionMaxLength:
active: false
maximumFunctionNameLength: 30
@ -223,25 +299,44 @@ naming:
minimumFunctionNameLength: 3
FunctionNaming:
active: true
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)'
excludeClassPattern: '$^'
ignoreOverridden: true
ignoreAnnotated: ['Composable']
FunctionParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
InvalidPackageDeclaration:
active: false
rootPackage: ''
LambdaParameterNaming:
active: false
parameterPattern: '[a-z][A-Za-z0-9]*|_'
MatchingDeclarationName:
active: true
mustBeFirst: true
MemberNameEqualsClassName:
active: false
ignoreOverridden: true
NoNameShadowing:
active: false
NonBooleanPropertyPrefixedWithIs:
active: false
ObjectPropertyNaming:
active: true
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
packagePattern: '^[a-z]+(\.[a-z][a-z0-9]*)*$'
packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
TopLevelPropertyNaming:
active: true
constantPattern: '[A-Z][_A-Z0-9]*'
propertyPattern: '[a-z][A-Za-z\d]*'