diff --git a/app/build.gradle b/app/build.gradle index 285f0d57d..1b66a53fd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -182,6 +182,7 @@ dependencies { implementation Deps.mozilla_concept_toolbar implementation Deps.mozilla_concept_storage implementation Deps.mozilla_concept_sync + implementation Deps.mozilla_concept_push implementation Deps.mozilla_browser_engine_gecko_nightly @@ -206,11 +207,13 @@ dependencies { implementation Deps.mozilla_feature_sitepermissions implementation Deps.mozilla_feature_intent implementation Deps.mozilla_feature_search + implementation Deps.mozilla_feature_sendtab implementation Deps.mozilla_feature_session implementation Deps.mozilla_feature_toolbar implementation Deps.mozilla_feature_tabs implementation Deps.mozilla_feature_downloads implementation Deps.mozilla_feature_prompts + implementation Deps.mozilla_feature_push implementation Deps.mozilla_feature_pwa implementation Deps.mozilla_feature_qr implementation Deps.mozilla_feature_readerview @@ -228,7 +231,10 @@ dependencies { implementation Deps.mozilla_support_ktx implementation Deps.mozilla_support_rustlog implementation Deps.mozilla_support_rusthttp + implementation Deps.mozilla_lib_crash + implementation Deps.mozilla_lib_push_firebase + implementation Deps.thirdparty_sentry implementation Deps.kotlin_stdlib @@ -236,8 +242,8 @@ dependencies { implementation Deps.androidx_appcompat implementation Deps.androidx_constraintlayout - implementation Deps.androidx_preference - implementation Deps.androidx_work_runtime + implementation Deps.androidx_preference_ktx + implementation Deps.androidx_work_runtime_ktx implementation Deps.google_material androidTestImplementation Deps.uiautomator diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3d113c59e..d33e0a548 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -104,6 +104,21 @@ android:label="@string/settings" android:theme="@style/AppTheme" android:parentActivityName=".BrowserActivity" /> + + + + + + + + + diff --git a/app/src/main/java/org/mozilla/reference/browser/BrowserActivity.kt b/app/src/main/java/org/mozilla/reference/browser/BrowserActivity.kt index b17641f2c..7c3d04445 100644 --- a/app/src/main/java/org/mozilla/reference/browser/BrowserActivity.kt +++ b/app/src/main/java/org/mozilla/reference/browser/BrowserActivity.kt @@ -25,7 +25,6 @@ import org.mozilla.reference.browser.browser.CrashIntegration import org.mozilla.reference.browser.ext.components import org.mozilla.reference.browser.ext.isCrashReportActive import org.mozilla.reference.browser.tabs.TabsTouchHelper -import org.mozilla.reference.browser.telemetry.DataReportingNotification /** * Activity that holds the [BrowserFragment]. @@ -61,7 +60,7 @@ open class BrowserActivity : AppCompatActivity() { lifecycle.addObserver(crashIntegration) } - DataReportingNotification.checkAndNotifyPolicy(this) + NotificationManager.checkAndNotifyPolicy(this) } override fun onBackPressed() { diff --git a/app/src/main/java/org/mozilla/reference/browser/BrowserApplication.kt b/app/src/main/java/org/mozilla/reference/browser/BrowserApplication.kt index b984e5ed3..c50b819b7 100644 --- a/app/src/main/java/org/mozilla/reference/browser/BrowserApplication.kt +++ b/app/src/main/java/org/mozilla/reference/browser/BrowserApplication.kt @@ -5,6 +5,7 @@ package org.mozilla.reference.browser import android.app.Application +import mozilla.components.concept.push.PushProcessor import mozilla.components.support.base.log.Log import mozilla.components.support.base.log.sink.AndroidLogSink import mozilla.components.support.ktx.android.content.isMainProcess @@ -36,6 +37,10 @@ open class BrowserApplication : Application() { components.analytics.initializeGlean() components.analytics.initializeExperiments() + + components.backgroundServices.pushFeature?.let { + PushProcessor.install(it) + } } override fun onTrimMemory(level: Int) { diff --git a/app/src/main/java/org/mozilla/reference/browser/IntentRequestCodes.kt b/app/src/main/java/org/mozilla/reference/browser/IntentRequestCodes.kt new file mode 100644 index 000000000..3c37121c7 --- /dev/null +++ b/app/src/main/java/org/mozilla/reference/browser/IntentRequestCodes.kt @@ -0,0 +1,12 @@ +/* + * 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.reference.browser + +object IntentRequestCodes { + const val REQUEST_CODE_DATA_REPORTING = 0 + const val REQUEST_CODE_SEND_TAB = 1 +} diff --git a/app/src/main/java/org/mozilla/reference/browser/NotificationManager.kt b/app/src/main/java/org/mozilla/reference/browser/NotificationManager.kt new file mode 100644 index 000000000..9f2fa73df --- /dev/null +++ b/app/src/main/java/org/mozilla/reference/browser/NotificationManager.kt @@ -0,0 +1,219 @@ +/* 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.reference.browser + +import android.annotation.TargetApi +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager as AndroidNotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.net.Uri +import android.os.Build +import android.os.Build.VERSION.SDK_INT +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.preference.PreferenceManager +import mozilla.components.concept.sync.Device +import mozilla.components.concept.sync.TabData +import mozilla.components.support.base.log.logger.Logger +import org.mozilla.reference.browser.IntentRequestCodes.REQUEST_CODE_DATA_REPORTING +import org.mozilla.reference.browser.IntentRequestCodes.REQUEST_CODE_SEND_TAB + +/** + * Manages notification channels and allows displaying different supported types of notifications. + */ +object NotificationManager { + + // Send Tab + private const val RECEIVE_TABS_TAG = "org.mozilla.reference.browser.receivedTabs" + private const val RECEIVE_TABS_CHANNEL_ID = "org.mozilla.reference.browser.ReceivedTabsChannel" + + // Data Reporting + private const val PRIVACY_NOTICE_URL = "https://www.mozilla.org/en-US/privacy/firefox/" + private const val DATA_REPORTING_VERSION = 1 + private const val DATA_REPORTING_TAG = "org.mozilla.reference.browser.DataReporting" + private const val DATA_REPORTING_NOTIFICATION_ID = 1 + private const val PREFS_POLICY_VERSION = "datareporting.policy.dataSubmissionPolicyVersion" + private const val PREFS_POLICY_NOTIFIED_TIME = + "datareporting.policy.dataSubmissionPolicyNotifiedTime" + + // Default + private const val NOTIFICATION_CHANNEL_ID = "default-notification-channel" + + // Use an incrementing notification ID since they have the same tag. + private var notificationIdCount = 0 + private val logger = Logger("NotificationManager") + + fun showReceivedTabs(context: Context, device: Device?, tabs: List) { + // In the future, experiment with displaying multiple tabs from the same device as as Notification Groups. + // For now, a single notification per tab received will suffice. + logger.debug("Showing ${tabs.size} tab(s) received from deviceID=${device?.id}") + + tabs.forEach { tab -> + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(tab.url)).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + val pendingIntent: PendingIntent = PendingIntent.getActivity( + context, + REQUEST_CODE_SEND_TAB, + intent, + PendingIntent.FLAG_ONE_SHOT + ) + val importance = if (SDK_INT >= Build.VERSION_CODES.N) { + // We pick 'IMPORTANCE_HIGH' priority because this is a user-triggered action that is + // expected to be part of a continuity flow. That is, user is expected to be waiting for + // this notification on their device; make it obvious. + AndroidNotificationManager.IMPORTANCE_HIGH + } else { + null + } + val channelId = getNotificationChannelId( + context, + RECEIVE_TABS_CHANNEL_ID, + context.getString(R.string.fxa_received_tab_channel_name), + context.getString(R.string.fxa_received_tab_channel_description), + importance + ) + + val builder = NotificationCompat.Builder(context, channelId) + .setSmallIcon(R.drawable.ic_notification) + .setSendTabTitle(context, device, tab) + .setWhen(System.currentTimeMillis()) + .setContentText(tab.url) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setDefaults(Notification.DEFAULT_VIBRATE or Notification.DEFAULT_SOUND) + .setCategory(NotificationCompat.CATEGORY_REMINDER) + + NotificationManagerCompat.from(context).notify( + RECEIVE_TABS_TAG, + notificationIdCount++, + builder.build() + ) + } + } + + fun checkAndNotifyPolicy(context: Context) { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val currentVersion = preferences.getInt(PREFS_POLICY_VERSION, -1) + + if (currentVersion < 1) { + // This is a first run, so notify user about data policy. + notifyDataPolicy(context, preferences) + } + } + + /** + * Launch a notification of the data policy, and record notification time and version. + */ + private fun notifyDataPolicy(context: Context, preferences: SharedPreferences) { + val resources = context.resources + + val notificationTitle = resources.getString(R.string.datareporting_notification_title) + val notificationSummary = resources.getString(R.string.datareporting_notification_summary) + + val intent = Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse(PRIVACY_NOTICE_URL) + setPackage(context.packageName) + } + + val pendingIntent = + PendingIntent.getActivity(context, REQUEST_CODE_DATA_REPORTING, intent, 0) + + val notificationBuilder = NotificationCompat.Builder( + context, + getNotificationChannelId(context) + ).apply { + setContentTitle(notificationTitle) + setContentText(notificationSummary) + setSmallIcon(R.drawable.ic_notification) + setAutoCancel(true) + setContentIntent(pendingIntent) + setStyle(NotificationCompat.BigTextStyle().bigText(notificationSummary)) + } + + NotificationManagerCompat.from(context) + .notify(DATA_REPORTING_TAG, DATA_REPORTING_NOTIFICATION_ID, notificationBuilder.build()) + + preferences.edit() + .putLong(PREFS_POLICY_NOTIFIED_TIME, System.currentTimeMillis()) + .putInt(PREFS_POLICY_VERSION, DATA_REPORTING_VERSION) + .apply() + } + + private fun getNotificationChannelId( + context: Context, + channelId: String = NOTIFICATION_CHANNEL_ID, + channelName: String = context.resources.getString(R.string.default_notification_channel), + description: String? = null, + channelImportance: Int? = null + ): String { + if (SDK_INT >= Build.VERSION_CODES.O) { + val importance = channelImportance ?: AndroidNotificationManager.IMPORTANCE_DEFAULT + createNotificationChannelIfNeeded( + context, + channelId, + channelName, + description, + importance + ) + } + + return channelId + } + + @TargetApi(Build.VERSION_CODES.O) + private fun createNotificationChannelIfNeeded( + context: Context, + channelId: String, + channelName: String, + channelDescription: String?, + importance: Int = AndroidNotificationManager.IMPORTANCE_DEFAULT + ) { + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as AndroidNotificationManager + + if (null != notificationManager.getNotificationChannel(channelId)) { + return + } + + val channel = NotificationChannel( + channelId, + channelName, + importance + ).apply { + description = channelDescription + } + + notificationManager.createNotificationChannel(channel) + } + + private fun NotificationCompat.Builder.setSendTabTitle( + context: Context, + device: Device?, + tab: TabData + ): NotificationCompat.Builder { + device?.let { + setContentTitle( + context.getString( + R.string.fxa_tab_received_from_notification_name, + it.displayName + ) + ) + return this + } + + if (tab.title.isEmpty()) { + setContentTitle(context.getString(R.string.fxa_tab_received_notification_name)) + } else { + setContentTitle(tab.title) + } + return this + } +} diff --git a/app/src/main/java/org/mozilla/reference/browser/components/BackgroundServices.kt b/app/src/main/java/org/mozilla/reference/browser/components/BackgroundServices.kt index c5818eeec..f06c8547b 100644 --- a/app/src/main/java/org/mozilla/reference/browser/components/BackgroundServices.kt +++ b/app/src/main/java/org/mozilla/reference/browser/components/BackgroundServices.kt @@ -6,18 +6,27 @@ package org.mozilla.reference.browser.components import android.content.Context import android.os.Build +import androidx.lifecycle.ProcessLifecycleOwner import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import mozilla.components.browser.storage.sync.PlacesHistoryStorage +import mozilla.components.concept.sync.AccountObserver +import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.DeviceCapability import mozilla.components.concept.sync.DeviceType +import mozilla.components.concept.sync.OAuthAccount +import mozilla.components.feature.push.AutoPushFeature +import mozilla.components.feature.push.PushConfig +import mozilla.components.feature.sendtab.SendTabFeature import mozilla.components.service.fxa.DeviceConfig import mozilla.components.service.fxa.ServerConfig import mozilla.components.service.fxa.SyncConfig import mozilla.components.service.fxa.SyncEngine import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.service.fxa.sync.GlobalSyncableStoreProvider +import org.mozilla.reference.browser.push.FirebasePush +import org.mozilla.reference.browser.NotificationManager /** * Component group for background services. These are components that need to be accessed from @@ -48,18 +57,65 @@ class BackgroundServices( syncPeriodInMinutes = 240L ) // four hours - val accountManager = FxaAccountManager( - context, - serverConfig, - deviceConfig, - syncConfig, - // We don't need to specify this explicitly, but `syncConfig` may be disabled due to an 'experiments' - // flag. In that case, sync scope necessary for syncing won't be acquired during authentication - // unless we explicitly specify it below. - // This is a good example of an information leak at the API level. - // See https://github.com/mozilla-mobile/android-components/issues/3732 - setOf("https://identity.mozilla.com/apps/oldsync") - ).also { - CoroutineScope(Dispatchers.Main).launch { it.initAsync().await() } + val accountManager by lazy { + FxaAccountManager( + context, + serverConfig, + deviceConfig, + syncConfig, + // We don't need to specify this explicitly, but `syncConfig` may be disabled due to an 'experiments' + // flag. In that case, sync scope necessary for syncing won't be acquired during authentication + // unless we explicitly specify it below. + // This is a good example of an information leak at the API level. + // See https://github.com/mozilla-mobile/android-components/issues/3732 + setOf("https://identity.mozilla.com/apps/oldsync") + ).also { + // We don't need the push service unless we're signed in. + it.register(pushServiceObserver, ProcessLifecycleOwner.get(), false) + + // Initializing the feature allows it to start observing events as needed. + SendTabFeature(it, pushFeature) { device, tabs -> + NotificationManager.showReceivedTabs(context, device, tabs) + } + + CoroutineScope(Dispatchers.Main).launch { it.initAsync().await() } + } + } + + val pushFeature by lazy { + pushConfig?.let { config -> + AutoPushFeature(context, pushService, config) + } + } + + /** + * The push configuration data class used to initialize the AutoPushFeature. + * + * If we have the `project_id` resource, then we know that the Firebase configuration and API + * keys are available for the FCM service to be used. + */ + private val pushConfig by lazy { + val resId = context.resources.getIdentifier("project_id", "string", context.packageName) + if (resId == 0) { + return@lazy null + } + val projectId = context.resources.getString(resId) + PushConfig(projectId) + } + + private val pushService by lazy { FirebasePush() } + + private val pushServiceObserver by lazy { + object : AccountObserver { + override fun onAuthenticated(account: OAuthAccount, authType: AuthType) { + if (authType != AuthType.Existing) { + pushService.start(context) + } + } + + override fun onLoggedOut() { + pushService.stop() + } + } } } diff --git a/app/src/main/java/org/mozilla/reference/browser/push/FirebasePush.kt b/app/src/main/java/org/mozilla/reference/browser/push/FirebasePush.kt new file mode 100644 index 000000000..69f62c4af --- /dev/null +++ b/app/src/main/java/org/mozilla/reference/browser/push/FirebasePush.kt @@ -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.reference.browser.push + +import mozilla.components.lib.push.firebase.AbstractFirebasePushService + +class FirebasePush : AbstractFirebasePushService() diff --git a/app/src/main/java/org/mozilla/reference/browser/telemetry/DataReportingNotification.kt b/app/src/main/java/org/mozilla/reference/browser/telemetry/DataReportingNotification.kt deleted file mode 100644 index c65a8c2e7..000000000 --- a/app/src/main/java/org/mozilla/reference/browser/telemetry/DataReportingNotification.kt +++ /dev/null @@ -1,101 +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.reference.browser.telemetry - -import android.annotation.TargetApi -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import android.net.Uri -import android.os.Build -import android.preference.PreferenceManager -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import org.mozilla.reference.browser.R - -/** - * Data Reporting notification to be shown on the first app start and whenever the data policy version changes. - */ -object DataReportingNotification { - private const val PREFS_POLICY_NOTIFIED_TIME = "datareporting.policy.dataSubmissionPolicyNotifiedTime" - private const val PREFS_POLICY_VERSION = "datareporting.policy.dataSubmissionPolicyVersion" - - private const val PRIVACY_NOTICE_URL = "https://www.mozilla.org/en-US/privacy/firefox/" - - private const val DATA_REPORTING_VERSION = 1 - - private const val NOTIFICATION_ID = 1 - - private const val NOTIFICATION_CHANNEL_ID = "default-notification-channel" - - fun checkAndNotifyPolicy(context: Context) { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - val currentVersion = preferences.getInt(PREFS_POLICY_VERSION, -1) - - if (currentVersion < 1) { - // This is a first run, so notify user about data policy. - notifyDataPolicy(context, preferences) - } - } - - /** - * Launch a notification of the data policy, and record notification time and version. - */ - private fun notifyDataPolicy(context: Context, preferences: SharedPreferences) { - val resources = context.resources - - val notificationTitle = resources.getString(R.string.datareporting_notification_title) - val notificationSummary = resources.getString(R.string.datareporting_notification_summary) - - val intent = Intent(Intent.ACTION_VIEW).apply { - data = Uri.parse(PRIVACY_NOTICE_URL) - setPackage(context.packageName) - } - - val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0) - - val notificationBuilder = NotificationCompat.Builder(context, getNotificationChannelId(context)) - .setContentTitle(notificationTitle) - .setContentText(notificationSummary) - .setSmallIcon(R.drawable.ic_notification) - .setAutoCancel(true) - .setContentIntent(pendingIntent) - .setStyle(NotificationCompat.BigTextStyle().bigText(notificationSummary)) - - NotificationManagerCompat.from(context) - .notify(NOTIFICATION_ID, notificationBuilder.build()) - - preferences.edit() - .putLong(PREFS_POLICY_NOTIFIED_TIME, System.currentTimeMillis()) - .putInt(PREFS_POLICY_VERSION, DATA_REPORTING_VERSION) - .apply() - } - - private fun getNotificationChannelId(context: Context): String { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createNotificationChannelIfNeeded(context) - } - - return NOTIFICATION_CHANNEL_ID - } - - @TargetApi(Build.VERSION_CODES.O) - private fun createNotificationChannelIfNeeded(context: Context) { - val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - if (null != notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)) { - return - } - - val channel = NotificationChannel( - NOTIFICATION_CHANNEL_ID, - context.resources.getString(R.string.default_notification_channel), - NotificationManager.IMPORTANCE_DEFAULT) - - notificationManager.createNotificationChannel(channel) - } -} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 17fb91d62..e92469120 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -155,4 +155,14 @@ Share with… Copied! + + + Received tabs + + Notifications for tabs received from other Firefox devices. + + Tab Received + + Tab from %s + diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 0254c9b2e..aa4fffa04 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -39,6 +39,7 @@ object Deps { const val mozilla_concept_toolbar = "org.mozilla.components:concept-toolbar:${Versions.mozilla_android_components}" const val mozilla_concept_storage = "org.mozilla.components:concept-storage:${Versions.mozilla_android_components}" const val mozilla_concept_sync = "org.mozilla.components:concept-sync:${Versions.mozilla_android_components}" + const val mozilla_concept_push = "org.mozilla.components:concept-push:${Versions.mozilla_android_components}" const val mozilla_browser_awesomebar = "org.mozilla.components:browser-awesomebar:${Versions.mozilla_android_components}" const val mozilla_browser_engine_gecko = "org.mozilla.components:browser-engine-gecko:${Versions.mozilla_android_components}" @@ -65,12 +66,14 @@ object Deps { const val mozilla_feature_sitepermissions = "org.mozilla.components:feature-sitepermissions:${Versions.mozilla_android_components}" const val mozilla_feature_intent = "org.mozilla.components:feature-intent:${Versions.mozilla_android_components}" const val mozilla_feature_search = "org.mozilla.components:feature-search:${Versions.mozilla_android_components}" + const val mozilla_feature_sendtab = "org.mozilla.components:feature-sendtab:${Versions.mozilla_android_components}" const val mozilla_feature_session = "org.mozilla.components:feature-session:${Versions.mozilla_android_components}" const val mozilla_feature_toolbar = "org.mozilla.components:feature-toolbar:${Versions.mozilla_android_components}" const val mozilla_feature_tabs = "org.mozilla.components:feature-tabs:${Versions.mozilla_android_components}" const val mozilla_feature_downloads = "org.mozilla.components:feature-downloads:${Versions.mozilla_android_components}" const val mozilla_feature_storage = "org.mozilla.components:feature-storage:${Versions.mozilla_android_components}" const val mozilla_feature_prompts = "org.mozilla.components:feature-prompts:${Versions.mozilla_android_components}" + const val mozilla_feature_push = "org.mozilla.components:feature-push:${Versions.mozilla_android_components}" const val mozilla_feature_pwa = "org.mozilla.components:feature-pwa:${Versions.mozilla_android_components}" const val mozilla_feature_qr = "org.mozilla.components:feature-qr:${Versions.mozilla_android_components}" const val mozilla_feature_readerview = "org.mozilla.components:feature-readerview:${Versions.mozilla_android_components}" @@ -90,13 +93,14 @@ object Deps { const val mozilla_support_rusthttp = "org.mozilla.components:support-rusthttp:${Versions.mozilla_android_components}" const val mozilla_lib_crash = "org.mozilla.components:lib-crash:${Versions.mozilla_android_components}" + const val mozilla_lib_push_firebase = "org.mozilla.components:lib-push-firebase:${Versions.mozilla_android_components}" const val thirdparty_sentry = "io.sentry:sentry-android:${Versions.thirdparty_sentry}" const val androidx_appcompat = "androidx.appcompat:appcompat:${Versions.androidx_appcompat}" const val androidx_constraintlayout = "androidx.constraintlayout:constraintlayout:${Versions.androidx_constraintlayout}" - const val androidx_preference = "androidx.preference:preference-ktx:${Versions.androidx_preference}" - const val androidx_work_runtime = "androidx.work:work-runtime-ktx:${Versions.workmanager}" + const val androidx_preference_ktx = "androidx.preference:preference-ktx:${Versions.androidx_preference}" + const val androidx_work_runtime_ktx = "androidx.work:work-runtime-ktx:${Versions.workmanager}" const val google_material = "com.google.android.material:material:${Versions.google_material}" const val tools_androidgradle = "com.android.tools.build:gradle:${Versions.android_gradle_plugin}"