diff --git a/app/build.gradle b/app/build.gradle
index 53d547ba8..c995eaf6c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -429,10 +429,7 @@ dependencies {
implementation Deps.mozilla_support_utils
implementation Deps.mozilla_support_locale
- // We only care about support-migration in builds that will be overwriting Fennec.
- fennecProductionImplementation Deps.mozilla_support_migration
- fennecBetaImplementation Deps.mozilla_support_migration
- fennecNightlyImplementation Deps.mozilla_support_migration
+ implementation Deps.mozilla_support_migration
implementation Deps.mozilla_ui_colors
implementation Deps.mozilla_ui_icons
diff --git a/app/sampledata/migration_items b/app/sampledata/migration_items
new file mode 100644
index 000000000..0dd95cfbd
--- /dev/null
+++ b/app/sampledata/migration_items
@@ -0,0 +1,5 @@
+History
+Bookmarks
+Logins
+Open Tabs
+Settings
\ No newline at end of file
diff --git a/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt b/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt
index 6be96b51a..c48f62a08 100644
--- a/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt
+++ b/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt
@@ -52,7 +52,8 @@ class IntentReceiverActivity : Activity() {
)
}
- val intentProcessors = components.intentProcessors.externalAppIntentProcessors +
+ val intentProcessors = listOf(components.intentProcessors.migrationIntentProcessor) +
+ components.intentProcessors.externalAppIntentProcessors +
modeDependentProcessors +
NewTabShortcutIntentProcessor()
diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt
index 9371512b9..93f2edcc0 100644
--- a/app/src/main/java/org/mozilla/fenix/components/Components.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt
@@ -6,6 +6,7 @@ package org.mozilla.fenix.components
import android.content.Context
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
+import mozilla.components.support.migration.state.MigrationStore
import org.mozilla.fenix.test.Mockable
import org.mozilla.fenix.utils.ClipboardHandler
@@ -44,10 +45,12 @@ class Components(private val context: Context) {
useCases.sessionUseCases,
useCases.searchUseCases,
core.client,
- core.customTabsStore
+ core.customTabsStore,
+ migrationStore
)
}
val analytics by lazy { Analytics(context) }
val publicSuffixList by lazy { PublicSuffixList(context) }
val clipboardHandler by lazy { ClipboardHandler(context) }
+ val migrationStore by lazy { MigrationStore() }
}
diff --git a/app/src/main/java/org/mozilla/fenix/components/IntentProcessorType.kt b/app/src/main/java/org/mozilla/fenix/components/IntentProcessorType.kt
index f94637ff4..1adb25b6b 100644
--- a/app/src/main/java/org/mozilla/fenix/components/IntentProcessorType.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/IntentProcessorType.kt
@@ -7,10 +7,11 @@ package org.mozilla.fenix.components
import android.content.Intent
import mozilla.components.feature.intent.processing.IntentProcessor
import org.mozilla.fenix.HomeActivity
+import org.mozilla.fenix.MigrationProgressActivity
import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity
enum class IntentProcessorType {
- EXTERNAL_APP, NEW_TAB, OTHER;
+ EXTERNAL_APP, NEW_TAB, MIGRATION, OTHER;
/**
* The destination activity based on this intent
@@ -19,6 +20,7 @@ enum class IntentProcessorType {
get() = when (this) {
EXTERNAL_APP -> ExternalAppBrowserActivity::class.java.name
NEW_TAB, OTHER -> HomeActivity::class.java.name
+ MIGRATION -> MigrationProgressActivity::class.java.name
}
/**
@@ -27,7 +29,7 @@ enum class IntentProcessorType {
fun shouldOpenToBrowser(intent: Intent): Boolean = when (this) {
EXTERNAL_APP -> true
NEW_TAB -> intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY == 0
- OTHER -> false
+ MIGRATION, OTHER -> false
}
}
@@ -35,6 +37,7 @@ enum class IntentProcessorType {
* Classifies the [IntentProcessorType] based on the [IntentProcessor] that handled the [Intent].
*/
fun IntentProcessors.getType(processor: IntentProcessor?) = when {
+ migrationIntentProcessor == processor -> IntentProcessorType.MIGRATION
externalAppIntentProcessors.contains(processor) ||
customTabIntentProcessor == processor ||
privateCustomTabIntentProcessor == processor -> IntentProcessorType.EXTERNAL_APP
diff --git a/app/src/main/java/org/mozilla/fenix/components/IntentProcessors.kt b/app/src/main/java/org/mozilla/fenix/components/IntentProcessors.kt
index eb0a0e53d..3c5e12eff 100644
--- a/app/src/main/java/org/mozilla/fenix/components/IntentProcessors.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/IntentProcessors.kt
@@ -15,6 +15,8 @@ import mozilla.components.feature.pwa.intent.WebAppIntentProcessor
import mozilla.components.feature.pwa.intent.TrustedWebActivityIntentProcessor
import mozilla.components.feature.search.SearchUseCases
import mozilla.components.feature.session.SessionUseCases
+import mozilla.components.support.migration.MigrationIntentProcessor
+import mozilla.components.support.migration.state.MigrationStore
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.home.intent.FennecBookmarkShortcutsIntentProcessor
import org.mozilla.fenix.test.Mockable
@@ -29,7 +31,8 @@ class IntentProcessors(
private val sessionUseCases: SessionUseCases,
private val searchUseCases: SearchUseCases,
private val httpClient: Client,
- private val customTabsStore: CustomTabsServiceStore
+ private val customTabsStore: CustomTabsServiceStore,
+ private val migrationStore: MigrationStore
) {
/**
* Provides intent processing functionality for ACTION_VIEW and ACTION_SEND intents.
@@ -67,4 +70,8 @@ class IntentProcessors(
FennecBookmarkShortcutsIntentProcessor(sessionManager, sessionUseCases.loadUrl)
)
}
+
+ val migrationIntentProcessor by lazy {
+ MigrationIntentProcessor(migrationStore)
+ }
}
diff --git a/app/src/main/res/drawable/ic_firefox.xml b/app/src/main/res/drawable/ic_firefox.xml
new file mode 100644
index 000000000..3e9e0416e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_firefox.xml
@@ -0,0 +1,407 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_migration.xml b/app/src/main/res/layout/activity_migration.xml
new file mode 100644
index 000000000..054ab2ca4
--- /dev/null
+++ b/app/src/main/res/layout/activity_migration.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/migration_list_item.xml b/app/src/main/res/layout/migration_list_item.xml
new file mode 100644
index 000000000..29ed13b8e
--- /dev/null
+++ b/app/src/main/res/layout/migration_list_item.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cfd9a06a0..fada3ec72 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1117,4 +1117,17 @@
Saved %s
Deleted %s
+
+
+ Firefox logo
+
+ Welcome to an all-new Firefox
+
+ A completely redesigned browser awaits, with improved performance and features to help you do more online.\n\nPlease wait while we update Firefox with your
+
+ Updating %s…
+
+ Start %s
+
+ Migration status: %s
diff --git a/app/src/migration/AndroidManifest.xml b/app/src/migration/AndroidManifest.xml
index 91b8afc31..3ddb51069 100644
--- a/app/src/migration/AndroidManifest.xml
+++ b/app/src/migration/AndroidManifest.xml
@@ -13,6 +13,11 @@
android:name="org.mozilla.fenix.MigratingFenixApplication"
tools:replace="android:name">
+
+
+
diff --git a/app/src/migration/java/org/mozilla/fenix/MigratingFenixApplication.kt b/app/src/migration/java/org/mozilla/fenix/MigratingFenixApplication.kt
index 813f9de29..956b877b4 100644
--- a/app/src/migration/java/org/mozilla/fenix/MigratingFenixApplication.kt
+++ b/app/src/migration/java/org/mozilla/fenix/MigratingFenixApplication.kt
@@ -5,6 +5,7 @@
package org.mozilla.fenix
import android.content.Context
+import android.content.Intent
import kotlinx.coroutines.runBlocking
import mozilla.components.support.migration.FennecMigrator
import mozilla.components.support.migration.state.MigrationStore
@@ -47,6 +48,10 @@ class MigratingFenixApplication : FenixApplication() {
// The rest of the migrations can happen now.
migrationPushSubscriber.start()
migrator.startMigrationIfNeeded(migrationStore, MigrationService::class.java)
+
+ // Start migration UI
+ val intent = Intent(this, MigrationProgressActivity::class.java)
+ startActivity(intent)
}
private fun migrateBlocking() {
diff --git a/app/src/migration/java/org/mozilla/fenix/MigrationProgressActivity.kt b/app/src/migration/java/org/mozilla/fenix/MigrationProgressActivity.kt
new file mode 100644
index 000000000..50e40c4df
--- /dev/null
+++ b/app/src/migration/java/org/mozilla/fenix/MigrationProgressActivity.kt
@@ -0,0 +1,148 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.android.synthetic.main.activity_migration.*
+import kotlinx.android.synthetic.main.migration_list_item.view.*
+import mozilla.components.support.migration.AbstractMigrationProgressActivity
+import mozilla.components.support.migration.Migration
+import mozilla.components.support.migration.Migration.Bookmarks
+import mozilla.components.support.migration.Migration.History
+import mozilla.components.support.migration.Migration.Logins
+import mozilla.components.support.migration.Migration.Settings
+import mozilla.components.support.migration.MigrationResults
+import mozilla.components.support.migration.state.MigrationProgress
+import mozilla.components.support.migration.state.MigrationStore
+import org.mozilla.fenix.ext.components
+
+class MigrationProgressActivity : AbstractMigrationProgressActivity() {
+ private val statusAdapter = MigrationStatusAdapter()
+ override val store: MigrationStore by lazy { components.migrationStore }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setContentView(R.layout.activity_migration)
+ init()
+ super.onCreate(savedInstanceState)
+ }
+
+ fun init() {
+ migration_status_list.apply {
+ layoutManager = LinearLayoutManager(this@MigrationProgressActivity)
+ adapter = statusAdapter
+ }
+
+ migration_button.apply {
+ setOnClickListener {
+ finish()
+ overridePendingTransition(0, 0)
+
+ // If we received a user-initiated intent, throw this back to the intent receiver.
+ if (intent.hasExtra(HomeActivity.OPEN_TO_BROWSER)) {
+ intent.setClassName(applicationContext, IntentReceiverActivity::class.java.name)
+ startActivity(intent)
+ }
+ }
+ text = getString(
+ R.string.migration_updating_app_button_text,
+ getString(R.string.app_name)
+ )
+ }
+ }
+
+ override fun onWindowFocusChanged(hasFocus: Boolean) {
+ super.onWindowFocusChanged(hasFocus)
+ if (hasFocus) {
+ // Enables sticky immersive mode.
+ window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_FULLSCREEN)
+ }
+ }
+
+ override fun onMigrationCompleted() {
+ // Enable clicking the finish button
+ migration_button.apply {
+ isEnabled = true
+ text = getString(R.string.migration_update_app_button, getString(R.string.app_name))
+ setBackgroundColor(ContextCompat.getColor(context, R.color.button_text_color))
+ setTextColor(ContextCompat.getColor(context, R.color.white_color))
+ }
+ }
+
+ override fun onMigrationStateChanged(progress: MigrationProgress, results: MigrationResults) {
+ statusAdapter.submitList(results.toItemList())
+ }
+}
+
+// These are the only items we want to show migrating in the UI.
+internal val whiteList = mapOf(
+ Bookmarks to R.string.preferences_sync_bookmarks,
+ History to R.string.preferences_sync_history,
+ Logins to R.string.preferences_sync_logins,
+ Settings to R.string.settings_title
+)
+
+internal fun MigrationResults.toItemList() = filterKeys {
+ whiteList.keys.contains(it)
+}.map { (type, status) ->
+ MigrationItem(
+ type,
+ status.success
+ )
+}
+
+internal data class MigrationItem(val migration: Migration, val status: Boolean)
+
+internal class MigrationStatusAdapter :
+ ListAdapter(DiffCallback) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
+
+ return ViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+
+ override fun getItemViewType(position: Int): Int = R.layout.migration_list_item
+
+ class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ private val context = view.context
+ private val title = view.migration_item_name
+ private val status = view.migration_status_image
+
+ fun bind(item: MigrationItem) {
+ // Get the resource ID for the item.
+ val migrationText = whiteList[item.migration]?.run {
+ context.getString(this)
+ } ?: ""
+ title.text = migrationText
+ status.visibility = if (item.status) View.VISIBLE else View.INVISIBLE
+ status.contentDescription =
+ context.getString(R.string.migration_icon_description, migrationText)
+ }
+ }
+
+ private object DiffCallback : DiffUtil.ItemCallback() {
+
+ override fun areItemsTheSame(oldItem: MigrationItem, newItem: MigrationItem) =
+ oldItem.migration.javaClass.simpleName == newItem.migration.javaClass.simpleName
+
+ override fun areContentsTheSame(oldItem: MigrationItem, newItem: MigrationItem) =
+ oldItem.migration.javaClass.simpleName == newItem.migration.javaClass.simpleName &&
+ oldItem.status == newItem.status
+ }
+}
diff --git a/app/src/migration/java/org/mozilla/fenix/MigrationService.kt b/app/src/migration/java/org/mozilla/fenix/MigrationService.kt
index 5060eb821..5bf4d3dcd 100644
--- a/app/src/migration/java/org/mozilla/fenix/MigrationService.kt
+++ b/app/src/migration/java/org/mozilla/fenix/MigrationService.kt
@@ -6,11 +6,12 @@ package org.mozilla.fenix
import mozilla.components.support.migration.AbstractMigrationService
import mozilla.components.support.migration.state.MigrationStore
+import org.mozilla.fenix.ext.components
/**
* Background service for running the migration from legacy Firefox for Android (Fennec).
*/
class MigrationService : AbstractMigrationService() {
override val migrator by lazy { getMigratorFromApplication() }
- override val store: MigrationStore by lazy { getMigrationStoreFromApplication() }
+ override val store: MigrationStore by lazy { components.migrationStore }
}