From b89afe7b7ce4d7cccd110b591d5902e63395e722 Mon Sep 17 00:00:00 2001 From: Jonathan Almeida Date: Wed, 15 Jan 2020 00:59:08 -0500 Subject: [PATCH] For #7092: Add initial Migration UI --- app/build.gradle | 5 +- app/sampledata/migration_items | 5 + .../mozilla/fenix/IntentReceiverActivity.kt | 3 +- .../mozilla/fenix/components/Components.kt | 5 +- .../fenix/components/IntentProcessorType.kt | 7 +- .../fenix/components/IntentProcessors.kt | 9 +- app/src/main/res/drawable/ic_firefox.xml | 407 ++++++++++++++++++ .../main/res/layout/activity_migration.xml | 77 ++++ .../main/res/layout/migration_list_item.xml | 35 ++ app/src/main/res/values/strings.xml | 13 + app/src/migration/AndroidManifest.xml | 5 + .../fenix/MigratingFenixApplication.kt | 5 + .../fenix/MigrationProgressActivity.kt | 148 +++++++ .../org/mozilla/fenix/MigrationService.kt | 3 +- 14 files changed, 717 insertions(+), 10 deletions(-) create mode 100644 app/sampledata/migration_items create mode 100644 app/src/main/res/drawable/ic_firefox.xml create mode 100644 app/src/main/res/layout/activity_migration.xml create mode 100644 app/src/main/res/layout/migration_list_item.xml create mode 100644 app/src/migration/java/org/mozilla/fenix/MigrationProgressActivity.kt 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 } }