diff --git a/AUTHORS b/AUTHORS
index b1483b4..417bae6 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -665,6 +665,7 @@
 Lucie Brozkova <lucinka.brozkova@gmail.com>
 Luiz Von Dentz <luiz.von.dentz@intel.com>
 Luka Dojcilovic <l.dojcilovic@gmail.com>
+Lukas Lihotzki <lukas@lihotzki.de>
 Lukasz Krakowiak <lukasz.krakowiak@mobica.com>
 Luke Inman-Semerau <luke.semerau@gmail.com>
 Luke Seunghoe Gu <gulukesh@gmail.com>
diff --git a/BUILD.gn b/BUILD.gn
index 863e91c..291e3be 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -367,7 +367,6 @@
       "//base/android/linker:chromium_android_linker",
       "//build/android/gyp/test:hello_world",
       "//build/android/stacktrace:java_deobfuscate",
-      "//build/android/test:android_nocompile_tests",
       "//build/config/android/test/proto:test_build_protos",
       "//chrome/android/monochrome:monochrome_public_apk_checker",
       "//chrome/android/webapk/shell_apk:maps_go_webapk",
@@ -389,7 +388,6 @@
       "//tools/android:push_apps_to_background",
       "//tools/android/audio_focus_grabber:audio_focus_grabber_apk",
       "//tools/android/customtabs_benchmark:customtabs_benchmark_apk",
-      "//tools/android/errorprone_plugin/test:errorprone_plugin_tests",
       "//tools/android/kerberos/SpnegoAuthenticator:spnego_authenticator_apk",
       "//ui/android:ui_junit_tests",
       "//weblayer/public/java:client_aar",
diff --git a/DEPS b/DEPS
index 9ca28a7..70eb4ea 100644
--- a/DEPS
+++ b/DEPS
@@ -92,6 +92,12 @@
   # restricted to Googlers only.
   'checkout_chromium_password_manager_test_dependencies': False,
 
+  # By default, do not check out Chromium Enterprise File System Connector
+  # captured sites test ependencies. These dependencies include a large number
+  # of large web capture files. Captured sites test dependencies are also
+  # restricted to Googlers only.
+  'checkout_chromium_fsc_test_dependencies': False,
+
   # By default, do not check out Google Benchmark. The library is only used by a
   # few specialized benchmarks that most developers do not interact with. Will
   # be overridden by gclient variables.
@@ -190,7 +196,7 @@
   # luci-go CIPD package version.
   # Make sure the revision is uploaded by infra-packagers builder.
   # https://ci.chromium.org/p/infra-internal/g/infra-packagers/console
-  'luci_go': 'git_revision:6387586e5b3279aebdf22bdab7ae619dbc156b66',
+  'luci_go': 'git_revision:9ee8b1d719c0d3c268e0e19282351ca78024af2d',
 
   # This can be overridden, e.g. with custom_vars, to build clang from HEAD
   # instead of downloading the prebuilt pinned revision.
@@ -222,11 +228,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '5caab8eea6e6e236bd1df2834ac43e95e10260f2',
+  'skia_revision': 'd090aa7feee336b4716edc23a1e99125a5a9c642',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'd44c0c48b9644c3e085a42a7dbb97b5fdb38fc1d',
+  'v8_revision': 'c26eb95a3425602903f447d4fd3f8f1cfde43937',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -238,11 +244,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '7a7993b54e62b85ddabe8dfcf7788174bf36b80f',
+  'swiftshader_revision': '915947134128e98746890355761ba1d67f9a1d70',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '178a16eb4c3cdd7fd2c821e2e6bbc506a50086dd',
+  'pdfium_revision': '7a6289c5ace52cf88f0e19caa5f78b7c15d0e7a6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -273,7 +279,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': '86b9c9347f99174f4fea3e9deca5800e57a987f2',
+  'freetype_revision': '801cd842e27c85cb1d5000f6397f382ffe295daa',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -293,7 +299,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '5cb305306ad74c3b68e432ee221a1943dd79b64d',
+  'catapult_revision': '9ac1fdf373609aac2e866e298067d78e053b6527',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -301,7 +307,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'd2002ce81eb3ea19488b73979c07e5632c863261',
+  'devtools_frontend_revision': '01a52efc6bd7cf995e2caa76835e8457d3d5107a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -341,7 +347,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '67035040f687fddbf38b7675c4ab23fa20b1fdaf',
+  'dawn_revision': '2d41f8c1df45b1aa642b5149060a8ec9ad9a0c63',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -357,7 +363,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling wuffs
   # and whatever else without interference from each other.
-  'wuffs_revision': 'd0451190ca0a4d0566d142261548cc264819f6c4',
+  'wuffs_revision': '1b9a6cb85b9f359595bd264d4c95da5d1291adc5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libgifcodec
   # and whatever else without interference from each other.
@@ -540,6 +546,29 @@
     'dep_type': 'cipd',
   },
 
+  'src/chrome/test/data/enterprise/connectors/file_system/captured_sites': {
+    'packages': [
+      {
+        'package': 'chromium/chrome/test/data/enterprise/connectors/file_system/captured_sites',
+        'version': 'VQWPovF2DbgjrWMu_FXXced70SojMAV5I4T--yHEHsAC',
+      }
+    ],
+    'condition': 'checkout_chromium_fsc_test_dependencies',
+    'dep_type': 'cipd',
+  },
+
+  'src/chrome/test/data/enterprise/connectors/file_system/downloads/cipd': {
+    'packages': [
+      {
+        'package': 'chromium/chrome/test/data/enterprise/connectors/file_system/downloads',
+        'version': 'OqoTmkXSZL8TiU2yFt3j6fKGoLwXYCWJXcFXg4L2b_wC',
+      }
+    ],
+    'condition': 'checkout_chromium_fsc_test_dependencies',
+    'dep_type': 'cipd',
+  },
+
+
   'src/chrome/test/data/perf/canvas_bench':
     Var('chromium_git') + '/chromium/canvas_bench.git' + '@' + 'a7b40ea5ae0239517d78845a5fc9b12976bfc732',
 
@@ -581,7 +610,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'd43fbda4bf3e8c07fd8e5feec2bc8fe98d5b6956',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '1c08fa7e015592aaf1de8f855aecdd40ac216a72',
       'condition': 'checkout_ios',
   },
 
@@ -738,7 +767,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'ak5sLJWTzTXot5KoqTvJsoSbbL-L5TYYMVsXEzVZDVsC',
+          'version': 'K8c7JN0QlH9y-iVNRC74RIWTTwcUwtM9CIwQ8vPQyFkC',
       },
     ],
     'condition': 'checkout_android',
@@ -903,7 +932,7 @@
   },
 
   'src/third_party/breakpad/breakpad':
-    Var('chromium_git') + '/breakpad/breakpad.git' + '@' + 'b95c4868b10f69e642666742233aede1eb653012',
+    Var('chromium_git') + '/breakpad/breakpad.git' + '@' + '32096a2dc8f8a7d5aac4097e34912bb7e06a5277',
 
   'src/third_party/byte_buddy': {
       'packages': [
@@ -954,7 +983,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'f203f97cdc2be1d07151386f6f036fb23b3ffe46',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '300bd5fd4d772b54826eb2f98dcc3a6bfe47a129',
       'condition': 'checkout_chromeos',
   },
 
@@ -989,7 +1018,7 @@
     Var('chromium_git') + '/external/gitlab.com/libeigen/eigen.git' + '@' + '011e0db31d1bed8b7f73662be6d57d9f30fa457a',
 
   'src/third_party/emoji-metadata/src': {
-    'url': Var('chromium_git') + '/external/github.com/googlefonts/emoji-metadata' + '@' + 'ffd71d1b87a46f7abd8206a5d2c752150f8e8865',
+    'url': Var('chromium_git') + '/external/github.com/googlefonts/emoji-metadata' + '@' + '069b14c94db6c1625a143d9f82e07a08a29909cf',
     'condition': 'checkout_chromeos',
   },
 
@@ -1343,7 +1372,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '3dd5b80bc4f172dd82925bb259cb7c82348409c5',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + '0d8bd6b2f8ffde7cfc93a414e38bcc8e93f82699',
+    Var('chromium_git') + '/openscreen' + '@' + 'f9715c5369c7dca34da0cb7b416efe65ed52a474',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + 'bf21ccb1007bb531b45d9978919a56ea5059c245',
@@ -1360,7 +1389,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '99063db35d93f47568a608d87e3f3912db59d3dd',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'f7ba153308a223418921dff22a61527a00aea264',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1449,7 +1478,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': 'MokqYq5DyNXxFI3FakMgGOll9hAFBPgQxSW5GUZ-RjQC'
+              'version': 'jIoBgZ-iUWXLCCH8YkbLabPLzKXZ54b27lb6trJpzpUC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1553,7 +1582,7 @@
   'src/third_party/usrsctp/usrsctplib':
     Var('chromium_git') + '/external/github.com/sctplab/usrsctp' + '@' + '1ade45cbadfd19298d2c47dc538962d4425ad2dd',
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@df0528b581a1709ccad790c205d3c11d0b657ed6',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@fe4d8297589b45f87d7a9511ff1ef04c3077dbc4',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'f67d7fa397e83060b76a1ec53579116a0bbdff7a',
@@ -1592,7 +1621,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'f19dd8595c4cc6058ec47db576b292d072353e5e',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '3b35fbcb66fee8af58b0c0990527833f1dcbcd7b',
+    Var('webrtc_git') + '/src.git' + '@' + '6882a3f7d04865975f4c5eeee76fe324f3141521',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1619,7 +1648,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/linux-amd64',
-          'version': 'xmipJwFYy7RCKCxmBLpju5jREejhapvJrC5N9RBk5U0C',
+          'version': '4iy6PO3bO0fujafbQ4I4eWhD8ErkBKf5xAnGzPrziIUC',
         },
       ],
       'dep_type': 'cipd',
@@ -1629,7 +1658,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/windows-amd64',
-          'version': 'K72GhZxnuZBndzgkNJfk6J5jt6rAA81fXTObGscqWlIC',
+          'version': 'NsVk6Er035r7JVK4foqbqAksO65XEzgIT9Bjwf0Q1q0C',
         },
       ],
       'dep_type': 'cipd',
@@ -1639,7 +1668,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-amd64',
-          'version': '97FmuWGI2oBGB4m3uRheUVyFLSzY0tkbgTM-t_AdPHcC',
+          'version': '_9rtsnZzHBJ4851DtK33jnz7UzMZ77SqJ9sYpciBwiMC',
         },
       ],
       'dep_type': 'cipd',
@@ -1653,7 +1682,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@d776a052b8a501d8c2bf5d5ca5503f0da5cd44ef',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@ab758d53e34a2985054eacd2407fa021827b88fb',
     'condition': 'checkout_src_internal',
   },
 
@@ -1661,7 +1690,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/eche_app/app',
-        'version': 'VZRj2PzW9IXHskymVfyKGRcnFTxPhSAkTbgc12beEckC',
+        'version': 'WdRKA24WST2Vbw7zE7rv45ZRHKEOI-Z7l661X_48w3wC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1672,7 +1701,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'oaXK5N5cXxFJ_nE5koGGPT1IQumi_tYTc7suEbL2nOgC',
+        'version': 'ZlTGnqls3Wsawh9cehEo-7zcYTTr7gwL1n4a70U1r9UC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1683,7 +1712,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': '9kKuyr-hCQ0u1-4ohi2CMmkAwb1X7llLGb57NXYyZYEC',
+        'version': 'IMZXkU8_JS73gqHZnRpaL6aveE8-XLk2eBP55w83viEC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/WATCHLISTS b/WATCHLISTS
index 9c82151..5004d3d6 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -846,7 +846,7 @@
     },
     'cros_reporting': {
       'filepath': 'chrome/browser/policy/messaging_layer/'\
-                  '|chrome/browser/chromeos/policy/status_collector/'\
+                  '|chrome/browser/ash/policy/status_collector/'\
                   '|components/policy/proto/record',
     },
     'cros_sharesheet': {
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsAnchorViewTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsAnchorViewTest.java
index b325dbc..7adf42a 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsAnchorViewTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsAnchorViewTest.java
@@ -33,8 +33,10 @@
 
     @Before
     public void setUp() {
-        mContainerView = new FrameLayout(mActivityTestRule.getActivity());
-        mViewDelegate = new AwViewAndroidDelegate(mContainerView, null, null);
+        mActivityTestRule.runOnUiThread(() -> {
+            mContainerView = new FrameLayout(mActivityTestRule.getActivity());
+            mViewDelegate = new AwViewAndroidDelegate(mContainerView, null, null);
+        });
     }
 
     @Test
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsLifecycleNotifierTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsLifecycleNotifierTest.java
index 7219509..880374b 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsLifecycleNotifierTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsLifecycleNotifierTest.java
@@ -4,8 +4,6 @@
 
 package org.chromium.android_webview.test;
 
-import android.support.test.InstrumentationRegistry;
-
 import androidx.test.filters.SmallTest;
 
 import org.junit.Assert;
@@ -16,6 +14,7 @@
 import org.chromium.android_webview.AwContentsLifecycleNotifier;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Feature;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * AwContentsLifecycleNotifier tests.
@@ -47,7 +46,9 @@
     @Feature({"AndroidWebView"})
     public void testNotifierCreate() throws Throwable {
         LifecycleObserver observer = new LifecycleObserver();
-        AwContentsLifecycleNotifier.addObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            AwContentsLifecycleNotifier.addObserver(observer);
+        });
         Assert.assertFalse(AwContentsLifecycleNotifier.hasWebViewInstances());
 
         AwTestContainerView awTestContainerView =
@@ -55,7 +56,7 @@
         observer.mFirstWebViewCreatedCallback.waitForCallback(0, 1);
         Assert.assertTrue(AwContentsLifecycleNotifier.hasWebViewInstances());
 
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+        TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityTestRule.getActivity().removeAllViews());
         mActivityTestRule.destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
         observer.mLastWebViewDestroyedCallback.waitForCallback(0, 1);
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/devui/CrashesListFragmentTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/devui/CrashesListFragmentTest.java
index db7f241..8c2f363 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/devui/CrashesListFragmentTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/devui/CrashesListFragmentTest.java
@@ -78,6 +78,7 @@
 import org.chromium.base.FileUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.components.minidump_uploader.CrashFileManager;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -1049,6 +1050,7 @@
     @Test
     @MediumTest
     @Feature({"AndroidWebView"})
+    @DisabledTest(message = "https://crbug.com/1231595")
     public void testConsentErrorMessage_shown_canUseGms() throws Throwable {
         Context context = InstrumentationRegistry.getTargetContext();
 
diff --git a/android_webview/tools/cts_config/webview_cts_gcs_path.json b/android_webview/tools/cts_config/webview_cts_gcs_path.json
index 7daf0cd..a0bdc4d 100644
--- a/android_webview/tools/cts_config/webview_cts_gcs_path.json
+++ b/android_webview/tools/cts_config/webview_cts_gcs_path.json
@@ -71,6 +71,10 @@
           {
             "match": "android.webkit.cts.WebViewTest#testLoadDataWithBaseUrl",
             "_bug_id": "crbug.com/900915"
+          },
+          {
+            "match": "android.webkit.cts.WebViewTest#testFlingScroll",
+            "_bug_id": "crbug.com/1230911"
           }
         ]
       },
@@ -156,6 +160,10 @@
           {
             "match": "android.webkit.cts.WebViewTest#testLoadDataWithBaseUrl",
             "_bug_id": "crbug.com/900915"
+          },
+          {
+            "match": "android.webkit.cts.WebViewTest#testFlingScroll",
+            "_bug_id": "crbug.com/1230911"
           }
         ]
       },
@@ -184,7 +192,13 @@
     },
     "test_runs": [
       {
-        "apk": "android-cts/testcases/CtsWebkitTestCases.apk"
+        "apk": "android-cts/testcases/CtsWebkitTestCases.apk",
+        "excludes": [
+          {
+            "match": "android.webkit.cts.WebViewTest#testFlingScroll",
+            "_bug_id": "crbug.com/1230911"
+          }
+        ]
       },
       {
         "apk": "android-cts/testcases/CtsWidgetTestCases.apk",
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 1baf164..cae8f0dc 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -2785,6 +2785,8 @@
     "test/toplevel_window.h",
     "test/ui_controls_factory_ash.cc",
     "test/ui_controls_factory_ash.h",
+    "test/view_drawn_waiter.cc",
+    "test/view_drawn_waiter.h",
     "test_media_client.cc",
     "test_media_client.h",
     "test_screenshot_delegate.cc",
diff --git a/ash/app_list/BUILD.gn b/ash/app_list/BUILD.gn
index 2e15018..1f8cc8f 100644
--- a/ash/app_list/BUILD.gn
+++ b/ash/app_list/BUILD.gn
@@ -246,6 +246,7 @@
     "views/app_list_view_unittest.cc",
     "views/apps_grid_view_unittest.cc",
     "views/assistant/assistant_page_view_unittest.cc",
+    "views/continue_section_view_unittest.cc",
     "views/folder_header_view_unittest.cc",
     "views/privacy_container_view_unittest.cc",
     "views/result_selection_controller_unittest.cc",
diff --git a/ash/app_list/app_list_controller_impl_unittest.cc b/ash/app_list/app_list_controller_impl_unittest.cc
index f59d15e..c411252 100644
--- a/ash/app_list/app_list_controller_impl_unittest.cc
+++ b/ash/app_list/app_list_controller_impl_unittest.cc
@@ -22,6 +22,7 @@
 #include "ash/app_list/views/expand_arrow_view.h"
 #include "ash/app_list/views/paged_apps_grid_view.h"
 #include "ash/app_list/views/search_box_view.h"
+#include "ash/app_list/views/suggestion_chip_container_view.h"
 #include "ash/constants/ash_features.h"
 #include "ash/ime/ime_controller_impl.h"
 #include "ash/ime/test_ime_controller_client.h"
@@ -96,8 +97,12 @@
       ->GetKeyboardWindow();
 }
 
+AppsContainerView* GetAppsContainerView() {
+  return GetContentsView()->apps_container_view();
+}
+
 AppsGridView* GetAppsGridView() {
-  return GetContentsView()->apps_container_view()->apps_grid_view();
+  return GetAppsContainerView()->apps_grid_view();
 }
 
 void ShowAppListNow(AppListViewState state) {
@@ -1302,4 +1307,44 @@
   EXPECT_FALSE(controller->bubble_presenter_for_test()->IsShowing());
 }
 
+class AppListSortTest : public AshTestBase {
+ public:
+  AppListSortTest() {
+    feature_list_.InitAndEnableFeature(ash::features::kLauncherAppSort);
+  }
+  ~AppListSortTest() override = default;
+
+  views::View* GetLeftSortButton() {
+    return GetAppsContainerView()
+        ->sort_button_container_for_test()
+        ->children()[0];
+  }
+
+  views::View* GetRightSortButton() {
+    return GetAppsContainerView()
+        ->sort_button_container_for_test()
+        ->children()[1];
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Verifies basic UI elements for the app list sort.
+TEST_F(AppListSortTest, BasicUI) {
+  // Verify sort buttons in the peeking state.
+  ShowAppListNow(AppListViewState::kPeeking);
+  ASSERT_EQ(AppListViewState::kPeeking, GetAppListView()->app_list_state());
+  EXPECT_TRUE(GetLeftSortButton()->GetVisible());
+  EXPECT_TRUE(GetRightSortButton()->GetVisible());
+  DismissAppListNow();
+
+  // Verify sort buttons in the full screen state.
+  ShowAppListNow(AppListViewState::kFullscreenAllApps);
+  ASSERT_EQ(AppListViewState::kFullscreenAllApps,
+            GetAppListView()->app_list_state());
+  EXPECT_TRUE(GetLeftSortButton()->GetVisible());
+  EXPECT_TRUE(GetRightSortButton()->GetVisible());
+}
+
 }  // namespace ash
diff --git a/ash/app_list/bubble/app_list_bubble_assistant_page.cc b/ash/app_list/bubble/app_list_bubble_assistant_page.cc
index e128c118..4780c33 100644
--- a/ash/app_list/bubble/app_list_bubble_assistant_page.cc
+++ b/ash/app_list/bubble/app_list_bubble_assistant_page.cc
@@ -8,6 +8,8 @@
 
 #include "ash/app_list/views/assistant/assistant_dialog_plate.h"
 #include "ash/app_list/views/assistant/assistant_main_stage.h"
+#include "ash/assistant/ui/colors/assistant_colors.h"
+#include "ash/assistant/ui/colors/assistant_colors_util.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/compositor/layer.h"
@@ -25,9 +27,8 @@
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
 
-  // TODO(crbug.com/1216098): Dark background support. The assistant answer
-  // cards currently assume they are placed within a white container.
-  SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
+  SetBackground(views::CreateSolidBackground(assistant::ResolveAssistantColor(
+      assistant_colors::ColorName::kBgAssistantPlate)));
 
   dialog_plate_ =
       AddChildView(std::make_unique<AssistantDialogPlate>(delegate));
@@ -42,6 +43,13 @@
   dialog_plate_->RequestFocus();
 }
 
+void AppListBubbleAssistantPage::OnThemeChanged() {
+  views::View::OnThemeChanged();
+
+  background()->SetNativeControlColor(assistant::ResolveAssistantColor(
+      assistant_colors::ColorName::kBgAssistantPlate));
+}
+
 BEGIN_METADATA(AppListBubbleAssistantPage, views::View)
 END_METADATA
 
diff --git a/ash/app_list/bubble/app_list_bubble_assistant_page.h b/ash/app_list/bubble/app_list_bubble_assistant_page.h
index fab9255..4fa52dc 100644
--- a/ash/app_list/bubble/app_list_bubble_assistant_page.h
+++ b/ash/app_list/bubble/app_list_bubble_assistant_page.h
@@ -29,6 +29,7 @@
 
   // views::View:
   void RequestFocus() override;
+  void OnThemeChanged() override;
 
  private:
   // The text and microphone input area. Owned by views hierarchy.
diff --git a/ash/app_list/bubble/app_list_bubble_view.h b/ash/app_list/bubble/app_list_bubble_view.h
index 1759033..3621775 100644
--- a/ash/app_list/bubble/app_list_bubble_view.h
+++ b/ash/app_list/bubble/app_list_bubble_view.h
@@ -59,6 +59,7 @@
 
  private:
   friend class AppListTestHelper;
+  friend class AssistantTestApiImpl;
 
   AppListViewDelegate* const view_delegate_;
   SearchBoxView* search_box_view_ = nullptr;
diff --git a/ash/app_list/test/app_list_test_helper.cc b/ash/app_list/test/app_list_test_helper.cc
index cb3cabd..ba631d1 100644
--- a/ash/app_list/test/app_list_test_helper.cc
+++ b/ash/app_list/test/app_list_test_helper.cc
@@ -99,6 +99,10 @@
       ->apps_page_;
 }
 
+ContinueSectionView* AppListTestHelper::GetContinueSectionView() {
+  return GetBubbleAppsPage()->continue_section_;
+}
+
 RecentAppsView* AppListTestHelper::GetBubbleRecentAppsView() {
   return GetBubbleAppsPage()->recent_apps_;
 }
diff --git a/ash/app_list/test/app_list_test_helper.h b/ash/app_list/test/app_list_test_helper.h
index 689c95d..57df50c 100644
--- a/ash/app_list/test/app_list_test_helper.h
+++ b/ash/app_list/test/app_list_test_helper.h
@@ -17,6 +17,7 @@
 class AppListBubbleSearchPage;
 class AppListControllerImpl;
 class AppListView;
+class ContinueSectionView;
 class RecentAppsView;
 class ScrollableAppsGridView;
 class SearchBoxView;
@@ -74,6 +75,7 @@
   // Bubble launcher helpers. The bubble must be open before calling these.
   SearchBoxView* GetBubbleSearchBoxView();
   AppListBubbleAppsPage* GetBubbleAppsPage();
+  ContinueSectionView* GetContinueSectionView();
   RecentAppsView* GetBubbleRecentAppsView();
   ScrollableAppsGridView* GetScrollableAppsGridView();
   AppListBubbleSearchPage* GetBubbleSearchPage();
diff --git a/ash/app_list/views/apps_container_view.cc b/ash/app_list/views/apps_container_view.cc
index abb8d23..e905244 100644
--- a/ash/app_list/views/apps_container_view.cc
+++ b/ash/app_list/views/apps_container_view.cc
@@ -20,13 +20,17 @@
 #include "ash/app_list/views/paged_apps_grid_view.h"
 #include "ash/app_list/views/search_box_view.h"
 #include "ash/app_list/views/suggestion_chip_container_view.h"
+#include "ash/constants/ash_features.h"
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_switches.h"
+#include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/search_box/search_box_constants.h"
+#include "ash/style/ash_color_provider.h"
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animation_element.h"
 #include "ui/compositor/layer_animator.h"
@@ -37,7 +41,10 @@
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/strings/grit/ui_strings.h"
 #include "ui/views/accessibility/view_accessibility.h"
+#include "ui/views/animation/ink_drop.h"
+#include "ui/views/controls/highlight_path_generator.h"
 #include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/layout/box_layout.h"
 
 namespace ash {
 
@@ -61,6 +68,100 @@
 // The margins within the apps container for app list folder view.
 constexpr int kFolderMargin = 8;
 
+// The space between sort buttons.
+constexpr int kSortButtonSpacing = 10;
+
+// The preferred size of a sort button.
+constexpr int kSortButtonPreferredSize = 20;
+
+// SortButton ------------------------------------------------------------------
+
+// A button for sorting the app icons on the launcher. Shown only when the
+// launcher apps sort is enabled.
+class SortButton : public views::ImageButton {
+ public:
+  METADATA_HEADER(SortButton);
+
+  explicit SortButton(bool is_alphabetical)
+      : is_alphabetical_(is_alphabetical) {
+    SetPaintToLayer();
+    layer()->SetFillsBoundsOpaquely(false);
+    views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
+    SetPreferredSize(
+        gfx::Size(kSortButtonPreferredSize, kSortButtonPreferredSize));
+
+    // This view is used behind the feature flag and is immature. Therefore
+    // ignore it in a11y for now.
+    GetViewAccessibility().OverrideIsIgnored(true);
+  }
+  SortButton(const SortButton&) = delete;
+  SortButton& operator=(const SortButton&) = delete;
+  ~SortButton() override = default;
+
+  // views::View:
+  void OnThemeChanged() override {
+    views::View::OnThemeChanged();
+    AshColorProvider::Get()->DecorateIconButton(
+        /*button=*/this,
+        is_alphabetical_ ? kOverflowShelfRightIcon : kOverflowShelfLeftIcon,
+        /*toggled=*/false, GetPreferredSize().width());
+
+    auto* inkdrop_host = views::InkDrop::Get(this);
+    AshColorProvider::Get()->DecorateInkDrop(
+        inkdrop_host, AshColorProvider::kConfigBaseColor |
+                          AshColorProvider::kConfigHighlightOpacity |
+                          AshColorProvider::kConfigHighlightOpacity);
+    views::InstallFixedSizeCircleHighlightPathGenerator(
+        this, kSortButtonPreferredSize / 2);
+    views::InkDrop::UseInkDropForFloodFillRipple(inkdrop_host,
+                                                 /*highlight_on_hover=*/true,
+                                                 /*highlight_on_focus=*/true);
+  }
+
+ private:
+  // If true, apps are sorted by the app name alphabetical order; otherwise,
+  // apps are sorted by the app name reverse alphabetical order.
+  const bool is_alphabetical_;
+};
+
+BEGIN_METADATA(SortButton, views::View)
+END_METADATA
+
+// SortButtonContainer ---------------------------------------------------------
+
+class SortButtonContainer : public views::View {
+ public:
+  METADATA_HEADER(SortButtonContainer);
+
+  SortButtonContainer() {
+    // The layer is required in animation.
+    SetPaintToLayer(ui::LayerType::LAYER_NOT_DRAWN);
+
+    // Configure the layout.
+    auto box_layout = std::make_unique<views::BoxLayout>(
+        views::BoxLayout::Orientation::kHorizontal,
+        /*inside_border_insets=*/gfx::Insets(),
+        /*between_child_spacing=*/kSortButtonSpacing);
+    box_layout->set_main_axis_alignment(
+        views::BoxLayout::MainAxisAlignment::kCenter);
+    box_layout->set_cross_axis_alignment(
+        views::BoxLayout::CrossAxisAlignment::kCenter);
+    SetLayoutManager(std::move(box_layout));
+
+    // Add children.
+    AddChildView(std::make_unique<SortButton>(/*is_alphabetical_=*/false));
+    AddChildView(std::make_unique<SortButton>(/*is_alphabetical_=*/true));
+
+    GetViewAccessibility().OverrideIsIgnored(true);
+  }
+  SortButtonContainer(const SortButtonContainer&) = delete;
+  SortButtonContainer& operator=(const SortButtonContainer&) = delete;
+  ~SortButtonContainer() override = default;
+};
+
+BEGIN_METADATA(SortButtonContainer, views::View)
+END_METADATA
+
 }  // namespace
 
 AppsContainerView::AppsContainerView(ContentsView* contents_view,
@@ -95,6 +196,11 @@
   // The folder view is initially hidden.
   app_list_folder_view_->SetVisible(false);
 
+  if (features::IsLauncherAppSortEnabled()) {
+    sort_button_container_ =
+        AddChildView(std::make_unique<SortButtonContainer>());
+  }
+
   apps_grid_view_->SetModel(model);
   apps_grid_view_->SetItemList(model->top_level_item_list());
   SetShowState(SHOW_APPS, false);
@@ -236,6 +342,11 @@
   suggestion_chip_container_view_->SetY(target_suggestion_chip_y);
   animator.Run(offset, suggestion_chip_container_view_->layer());
 
+  if (features::IsLauncherAppSortEnabled()) {
+    sort_button_container_->SetY(target_suggestion_chip_y);
+    animator.Run(offset, sort_button_container_->layer());
+  }
+
   apps_grid_view_->SetY(suggestion_chip_container_view_->y() +
                         chip_grid_y_distance_);
   animator.Run(offset, apps_grid_view_->layer());
@@ -310,9 +421,23 @@
 
   // Layout page switcher.
   const int page_switcher_width = page_switcher_->GetPreferredSize().width();
-  page_switcher_->SetBoundsRect(gfx::Rect(
+  const gfx::Rect page_switcher_bounds(
       grid_rect.right() + GetAppListConfig().grid_to_page_switcher_margin(),
-      grid_rect.y(), page_switcher_width, grid_rect.height()));
+      grid_rect.y(), page_switcher_width, grid_rect.height());
+  page_switcher_->SetBoundsRect(page_switcher_bounds);
+
+  if (features::IsLauncherAppSortEnabled()) {
+    // Align `sort_button_container_` with `suggestion_chip_container_view_`
+    // horizontally; align `sort_button_container_` with `page_switcher_bounds`
+    // vertically on the right edge.
+    const int sort_button_container_width =
+        sort_button_container_->GetPreferredSize().width();
+    gfx::Rect sort_button_container_rect(
+        page_switcher_bounds.right() - sort_button_container_width,
+        suggestion_chip_container_view_->y(), sort_button_container_width,
+        chip_container_rect.height());
+    sort_button_container_->SetBoundsRect(sort_button_container_rect);
+  }
 
   switch (show_state_) {
     case SHOW_APPS:
diff --git a/ash/app_list/views/apps_container_view.h b/ash/app_list/views/apps_container_view.h
index da951f4..3345753b 100644
--- a/ash/app_list/views/apps_container_view.h
+++ b/ash/app_list/views/apps_container_view.h
@@ -119,9 +119,6 @@
                         const TransformAnimator& animator,
                         float default_offset) override;
 
-  SuggestionChipContainerView* suggestion_chip_container_view_for_test() {
-    return suggestion_chip_container_view_;
-  }
   PagedAppsGridView* apps_grid_view() { return apps_grid_view_; }
   FolderBackgroundView* folder_background_view() {
     return folder_background_view_;
@@ -129,6 +126,13 @@
   AppListFolderView* app_list_folder_view() { return app_list_folder_view_; }
   PageSwitcher* page_switcher() { return page_switcher_; }
 
+  views::View* sort_button_container_for_test() {
+    return sort_button_container_;
+  }
+  SuggestionChipContainerView* suggestion_chip_container_view_for_test() {
+    return suggestion_chip_container_view_;
+  }
+
   // Called by app list view when the app list config changes.
   void OnAppListConfigUpdated();
 
@@ -198,6 +202,7 @@
   AppListFolderView* app_list_folder_view_ = nullptr;
   PageSwitcher* page_switcher_ = nullptr;
   FolderBackgroundView* folder_background_view_ = nullptr;
+  views::View* sort_button_container_ = nullptr;
 
   ShowState show_state_ = SHOW_NONE;
 
diff --git a/ash/app_list/views/assistant/assistant_page_view_unittest.cc b/ash/app_list/views/assistant/assistant_page_view_unittest.cc
index 0b61519..aea3b87 100644
--- a/ash/app_list/views/assistant/assistant_page_view_unittest.cc
+++ b/ash/app_list/views/assistant/assistant_page_view_unittest.cc
@@ -19,6 +19,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "chromeos/services/assistant/public/cpp/assistant_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/events/event.h"
@@ -162,6 +163,10 @@
   DISALLOW_COPY_AND_ASSIGN(GestureEventForTest);
 };
 
+// Base class for tests of the embedded assistant page in:
+// - Legacy clamshell mode ("peeking launcher")
+// - Clamshell mode ("bubble launcher")
+// - Tablet mode
 class AssistantPageViewTest : public AssistantAshTestBase {
  public:
   AssistantPageViewTest() = default;
@@ -176,25 +181,6 @@
     EXPECT_TRUE(IsVisible());
   }
 
-  // Returns a point in the AppList, but outside the Assistant UI.
-  gfx::Point GetPointInAppListOutsideAssistantUi() {
-    gfx::Point result = GetPointOutside(page_view());
-
-    // Validity check
-    EXPECT_TRUE(app_list_view()->bounds().Contains(result));
-    EXPECT_FALSE(page_view()->bounds().Contains(result));
-
-    return result;
-  }
-
-  gfx::Point GetPointOutside(const views::View* view) {
-    return gfx::Point(view->origin().x() - 10, view->origin().y() - 10);
-  }
-
-  gfx::Point GetPointInside(const views::View* view) {
-    return view->GetBoundsInScreen().CenterPoint();
-  }
-
   void PressKey(ui::KeyboardCode key_code) {
     // Any key press consists of 2 events, namely |press| and |release|.
     GetEventGenerator()->PressKey(key_code, /*flags=*/ui::EF_NONE);
@@ -215,7 +201,7 @@
   }
 
   const views::View* GetFocusedView() {
-    return main_view()->GetFocusManager()->GetFocusedView();
+    return page_view()->GetWidget()->GetFocusManager()->GetFocusedView();
   }
 
   // Ensures the onboarding views will not be shown.
@@ -228,6 +214,17 @@
   DISALLOW_COPY_AND_ASSIGN(AssistantPageViewTest);
 };
 
+// Tests for the legacy non-bubble app list ("peeking launcher").
+// These tests can be deleted when features::kAppListBubble is fully launched.
+class AssistantPageNonBubbleTest : public AssistantPageViewTest {
+ public:
+  AssistantPageNonBubbleTest() {
+    scoped_feature_list_.InitAndDisableFeature(features::kAppListBubble);
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
 // Counts the number of Assistant interactions that are started.
 class AssistantInteractionCounter
     : private chromeos::assistant::AssistantInteractionSubscriber {
@@ -254,9 +251,7 @@
       interaction_observer_{this};
 };
 
-}  // namespace
-
-TEST_F(AssistantPageViewTest, ShouldStartInPeekingState) {
+TEST_F(AssistantPageNonBubbleTest, ShouldStartInPeekingState) {
   DoNotShowOnboardingViews();
 
   ShowAssistantUi();
@@ -264,7 +259,7 @@
   EXPECT_EQ(AppListViewState::kPeeking, app_list_view()->app_list_state());
 }
 
-TEST_F(AssistantPageViewTest, ShouldStartInHalfState) {
+TEST_F(AssistantPageNonBubbleTest, ShouldStartInHalfState) {
   SetOnboardingMode(AssistantOnboardingMode::kEducation);
 
   ShowAssistantUi();
@@ -272,7 +267,7 @@
   EXPECT_EQ(AppListViewState::kHalf, app_list_view()->app_list_state());
 }
 
-TEST_F(AssistantPageViewTest, ShouldStartAtMinimumHeight) {
+TEST_F(AssistantPageNonBubbleTest, ShouldStartAtMinimumHeight) {
   DoNotShowOnboardingViews();
 
   ShowAssistantUi();
@@ -281,7 +276,7 @@
   EXPECT_EQ(kMinHeightDip, main_view()->size().height());
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_F(AssistantPageNonBubbleTest,
        ShouldRemainAtMinimumHeightWhenDisplayingOneLiner) {
   DoNotShowOnboardingViews();
 
@@ -293,7 +288,7 @@
   EXPECT_EQ(kMinHeightDip, main_view()->size().height());
 }
 
-TEST_F(AssistantPageViewTest, ShouldGetBiggerWithMultilineText) {
+TEST_F(AssistantPageNonBubbleTest, ShouldGetBiggerWithMultilineText) {
   ShowAssistantUi();
 
   MockTextInteraction().WithTextResponse(
@@ -303,7 +298,7 @@
   EXPECT_EQ(kMaxHeightDip, main_view()->size().height());
 }
 
-TEST_F(AssistantPageViewTest, ShouldGetBiggerWhenWrappingTextLine) {
+TEST_F(AssistantPageNonBubbleTest, ShouldGetBiggerWhenWrappingTextLine) {
   ShowAssistantUi();
 
   MockTextInteraction().WithTextResponse(
@@ -315,7 +310,10 @@
   EXPECT_EQ(kMaxHeightDip, main_view()->size().height());
 }
 
-TEST_F(AssistantPageViewTest, ShouldNotRequestFocusWhenOtherAppWindowOpens) {
+// Only tested for non-bubble launcher because for the bubble launcher we always
+// close the bubble and it can't permanently steal focus from another window.
+TEST_F(AssistantPageNonBubbleTest,
+       ShouldNotRequestFocusWhenOtherAppWindowOpens) {
   // This tests the root cause of b/141945964.
   // Namely, the Assistant code should not request the focus while being closed.
   ShowAssistantUi();
@@ -337,13 +335,46 @@
   }
 }
 
-TEST_F(AssistantPageViewTest, ShouldFocusTextFieldWhenOpeningWithHotkey) {
+// Only tested for non-bubble launcher because for the bubble launcher we always
+// close the bubble and clear the input when we switch to tablet mode.
+TEST_F(AssistantPageNonBubbleTest,
+       ShouldNotClearQueryWhenSwitchingToTabletMode) {
+  const std::u16string query_text = u"unsubmitted query";
+  ShowAssistantUiInTextMode();
+  input_text_field()->SetText(query_text);
+
+  SetTabletMode(true);
+
+  EXPECT_HAS_FOCUS(input_text_field());
+  EXPECT_EQ(query_text, input_text_field()->GetText());
+}
+
+//------------------------------------------------------------------------------
+// Tests for the clamshell mode launcher, parameterized by the feature
+// kAppListBubble.
+class AssistantPageClamshellTest : public AssistantPageViewTest,
+                                   public testing::WithParamInterface<bool> {
+ public:
+  AssistantPageClamshellTest() {
+    if (GetParam())
+      scoped_feature_list_.InitAndEnableFeature(features::kAppListBubble);
+    else
+      scoped_feature_list_.InitAndDisableFeature(features::kAppListBubble);
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Bubble, AssistantPageClamshellTest, testing::Bool());
+
+TEST_P(AssistantPageClamshellTest, ShouldFocusTextFieldWhenOpeningWithHotkey) {
   ShowAssistantUi(AssistantEntryPoint::kHotkey);
 
   EXPECT_HAS_FOCUS(input_text_field());
 }
 
-TEST_F(AssistantPageViewTest, ShouldNotLoseTextfieldFocusWhenSendingTextQuery) {
+TEST_P(AssistantPageClamshellTest,
+       ShouldNotLoseTextfieldFocusWhenSendingTextQuery) {
   ShowAssistantUi();
 
   SendQueryThroughTextField("The query");
@@ -351,7 +382,7 @@
   EXPECT_HAS_FOCUS(input_text_field());
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldNotLoseTextfieldFocusWhenDisplayingResponse) {
   ShowAssistantUi();
 
@@ -360,7 +391,7 @@
   EXPECT_HAS_FOCUS(input_text_field());
 }
 
-TEST_F(AssistantPageViewTest, ShouldNotLoseTextfieldFocusWhenResizing) {
+TEST_P(AssistantPageClamshellTest, ShouldNotLoseTextfieldFocusWhenResizing) {
   ShowAssistantUi();
 
   MockTextInteraction().WithTextResponse(
@@ -370,7 +401,8 @@
   EXPECT_HAS_FOCUS(input_text_field());
 }
 
-TEST_F(AssistantPageViewTest, FocusShouldRemainInAssistantViewWhenPressingTab) {
+TEST_P(AssistantPageClamshellTest,
+       FocusShouldRemainInAssistantViewWhenPressingTab) {
   constexpr int kMaxIterations = 100;
   ShowAssistantUi();
 
@@ -391,7 +423,7 @@
   } while (focused_view != initial_focused_view);
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        FocusShouldCycleThroughOnboardingSuggestionsWhenPressingTab) {
   constexpr int kMaxIterations = 100;
 
@@ -411,8 +443,8 @@
   }
 
   // Verify we can cycle through them.
-  for (size_t i = 0; i < onboarding_suggestions.size(); ++i) {
-    ASSERT_EQ(GetFocusedView(), onboarding_suggestions.at(i));
+  for (auto* onboarding_suggestion : onboarding_suggestions) {
+    ASSERT_EQ(GetFocusedView(), onboarding_suggestion);
     PressKeyAndWait(ui::VKEY_TAB);
   }
 
@@ -424,13 +456,13 @@
   }
 }
 
-TEST_F(AssistantPageViewTest, ShouldFocusMicWhenOpeningWithHotword) {
+TEST_P(AssistantPageClamshellTest, ShouldFocusMicWhenOpeningWithHotword) {
   ShowAssistantUi(AssistantEntryPoint::kHotword);
 
   EXPECT_HAS_FOCUS(mic_view());
 }
 
-TEST_F(AssistantPageViewTest, ShouldShowGreetingLabelWhenOpening) {
+TEST_P(AssistantPageClamshellTest, ShouldShowGreetingLabelWhenOpening) {
   DoNotShowOnboardingViews();
 
   ShowAssistantUi();
@@ -439,14 +471,14 @@
   EXPECT_FALSE(onboarding_view()->IsDrawn());
 }
 
-TEST_F(AssistantPageViewTest, ShouldShowOnboardingWhenOpening) {
+TEST_P(AssistantPageClamshellTest, ShouldShowOnboardingWhenOpening) {
   ShowAssistantUi();
 
   EXPECT_TRUE(onboarding_view()->IsDrawn());
   EXPECT_FALSE(greeting_label()->IsDrawn());
 }
 
-TEST_F(AssistantPageViewTest, ShouldDismissGreetingLabelAfterQuery) {
+TEST_P(AssistantPageClamshellTest, ShouldDismissGreetingLabelAfterQuery) {
   DoNotShowOnboardingViews();
 
   ShowAssistantUi();
@@ -457,7 +489,7 @@
   EXPECT_FALSE(onboarding_view()->IsDrawn());
 }
 
-TEST_F(AssistantPageViewTest, ShouldDismissOnboardingAfterQuery) {
+TEST_P(AssistantPageClamshellTest, ShouldDismissOnboardingAfterQuery) {
   ShowAssistantUi();
 
   MockTextInteraction().WithTextResponse("The response");
@@ -466,7 +498,7 @@
   EXPECT_FALSE(greeting_label()->IsDrawn());
 }
 
-TEST_F(AssistantPageViewTest, ShouldShowGreetingLabelAgainAfterReopening) {
+TEST_P(AssistantPageClamshellTest, ShouldShowGreetingLabelAgainAfterReopening) {
   DoNotShowOnboardingViews();
 
   ShowAssistantUi();
@@ -483,7 +515,7 @@
   EXPECT_FALSE(onboarding_view()->IsDrawn());
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldNotShowGreetingLabelWhenOpeningFromSearchResult) {
   DoNotShowOnboardingViews();
 
@@ -493,7 +525,7 @@
   EXPECT_FALSE(onboarding_view()->IsDrawn());
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldNotShowOnboardingWhenOpeningFromSearchResult) {
   ShowAssistantUi(AssistantEntryPoint::kLauncherSearchResult);
 
@@ -501,7 +533,7 @@
   EXPECT_FALSE(greeting_label()->IsDrawn());
 }
 
-TEST_F(AssistantPageViewTest, ShouldShowOnboardingForNewUsers) {
+TEST_P(AssistantPageClamshellTest, ShouldShowOnboardingForNewUsers) {
   // A user is considered new if they haven't had an Assistant interaction in
   // the past 28 days.
   const base::Time new_user_cutoff =
@@ -524,7 +556,7 @@
   EXPECT_TRUE(onboarding_view()->IsDrawn());
 }
 
-TEST_F(AssistantPageViewTest, ShouldShowOnboardingUntilInteractionOccurs) {
+TEST_P(AssistantPageClamshellTest, ShouldShowOnboardingUntilInteractionOccurs) {
   SetTimeOfLastInteraction(base::Time::Now() - base::TimeDelta::FromDays(28));
   ShowAssistantUi();
 
@@ -549,7 +581,7 @@
   EXPECT_FALSE(onboarding_view()->IsDrawn());
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldShowOnboardingToExistingUsersIfShownPreviouslyInDifferentSession) {
   SetTimeOfLastInteraction(base::Time::Now());
   SetNumberOfSessionsWhereOnboardingShown(1);
@@ -571,7 +603,7 @@
   EXPECT_FALSE(onboarding_view()->IsDrawn());
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldNotShowOnboardingToExistingUsersIfShownPreviouslyInMaxSessions) {
   SetTimeOfLastInteraction(base::Time::Now());
   SetNumberOfSessionsWhereOnboardingShown(
@@ -586,7 +618,8 @@
   EXPECT_FALSE(onboarding_view()->IsDrawn());
 }
 
-TEST_F(AssistantPageViewTest, ShouldFocusMicViewWhenPressingVoiceInputToggle) {
+TEST_P(AssistantPageClamshellTest,
+       ShouldFocusMicViewWhenPressingVoiceInputToggle) {
   ShowAssistantUiInTextMode();
 
   ClickOnAndWait(voice_input_toggle());
@@ -594,7 +627,7 @@
   EXPECT_HAS_FOCUS(mic_view());
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldStartVoiceInteractionWhenPressingVoiceInputToggle) {
   ShowAssistantUiInTextMode();
 
@@ -603,7 +636,7 @@
   EXPECT_INTERACTION_OF_TYPE(AssistantInteractionType::kVoice);
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldStopVoiceInteractionWhenPressingKeyboardInputToggle) {
   ShowAssistantUiInVoiceMode();
   EXPECT_INTERACTION_OF_TYPE(AssistantInteractionType::kVoice);
@@ -613,7 +646,8 @@
   EXPECT_FALSE(current_interaction().has_value());
 }
 
-TEST_F(AssistantPageViewTest, ShouldShowOptInViewUnlessUserHasGivenConsent) {
+TEST_P(AssistantPageClamshellTest,
+       ShouldShowOptInViewUnlessUserHasGivenConsent) {
   ShowAssistantUi();
   const views::View* suggestion_chips = suggestion_chip_container();
   const views::View* opt_in = opt_in_view();
@@ -635,7 +669,8 @@
   EXPECT_TRUE(suggestion_chips->IsDrawn());
 }
 
-TEST_F(AssistantPageViewTest, ShouldSubmitQueryWhenClickingOnSuggestionChip) {
+TEST_P(AssistantPageClamshellTest,
+       ShouldSubmitQueryWhenClickingOnSuggestionChip) {
   ShowAssistantUi();
   ash::SuggestionChipView* suggestion_chip =
       CreateAndGetSuggestionChip("<suggestion chip query>");
@@ -646,7 +681,7 @@
   EXPECT_EQ("<suggestion chip query>", current_interaction()->query);
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldSubmitQueryWhenPressingEnterOnSuggestionChip) {
   ShowAssistantUi();
   ash::SuggestionChipView* suggestion_chip =
@@ -659,7 +694,7 @@
   EXPECT_EQ("<suggestion chip query>", current_interaction()->query);
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldNotSubmitQueryWhenPressingSpaceOnSuggestionChip) {
   ShowAssistantUi();
   ash::SuggestionChipView* suggestion_chip =
@@ -671,7 +706,7 @@
   EXPECT_NO_INTERACTION();
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldOnlySubmitOneQueryWhenClickingSuggestionChipMultipleTimes) {
   ShowAssistantUi();
   ash::SuggestionChipView* suggestion_chip =
@@ -686,7 +721,7 @@
   EXPECT_EQ(1, counter.interaction_count());
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldOnlySubmitQueryFromFirstSuggestionChipClickedOn) {
   ShowAssistantUi();
   MockTextInteraction()
@@ -709,7 +744,7 @@
   EXPECT_EQ("<first query>", current_interaction()->query);
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        SuggestionChipsShouldNotBeFocusableAfterSubmittingQuery) {
   ShowAssistantUi();
   MockTextInteraction()
@@ -728,7 +763,7 @@
   }
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldFocusTextFieldWhenSubmittingSuggestionChipInTextMode) {
   ShowAssistantUiInTextMode();
   ash::SuggestionChipView* suggestion_chip =
@@ -740,7 +775,7 @@
   EXPECT_HAS_FOCUS(input_text_field());
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldFocusMicWhenSubmittingSuggestionChipInVoiceMode) {
   ShowAssistantUi();
   ash::SuggestionChipView* suggestion_chip =
@@ -753,7 +788,7 @@
   EXPECT_HAS_FOCUS(mic_view());
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldFocusTextFieldWhenPressingKeyboardInputToggle) {
   ShowAssistantUiInVoiceMode();
 
@@ -762,6 +797,9 @@
   EXPECT_HAS_FOCUS(input_text_field());
 }
 
+// TODO(crbug.com/1229797): Switch to TEST_P and AssistantPageClamshellTest.
+// It fails with kAppListBubble enabled because the vertical position of the
+// suggestion chip doesn't match.
 TEST_F(AssistantPageViewTest,
        ShouldNotScrollSuggestionChipsWhenSubmittingQuery) {
   ShowAssistantUiInTextMode();
@@ -784,7 +822,7 @@
   EXPECT_EQ(initial_bounds, final_bounds);
 }
 
-TEST_F(AssistantPageViewTest, RememberAndShowHistory) {
+TEST_P(AssistantPageClamshellTest, RememberAndShowHistory) {
   ShowAssistantUiInTextMode();
   EXPECT_HAS_FOCUS(input_text_field());
 
@@ -811,18 +849,7 @@
   EXPECT_TRUE(input_text_field()->GetText().empty());
 }
 
-TEST_F(AssistantPageViewTest, ShouldNotClearQueryWhenSwitchingToTabletMode) {
-  const std::u16string query_text = u"unsubmitted query";
-  ShowAssistantUiInTextMode();
-  input_text_field()->SetText(query_text);
-
-  SetTabletMode(true);
-
-  EXPECT_HAS_FOCUS(input_text_field());
-  EXPECT_EQ(query_text, input_text_field()->GetText());
-}
-
-TEST_F(AssistantPageViewTest, ShouldHaveConversationStarters) {
+TEST_P(AssistantPageClamshellTest, ShouldHaveConversationStarters) {
   DoNotShowOnboardingViews();
 
   ShowAssistantUi();
@@ -831,7 +858,7 @@
   EXPECT_FALSE(GetSuggestionChips().empty());
 }
 
-TEST_F(AssistantPageViewTest,
+TEST_P(AssistantPageClamshellTest,
        ShouldNotHaveConversationStartersWhenShowingOnboarding) {
   ShowAssistantUi();
 
@@ -839,7 +866,7 @@
   EXPECT_TRUE(GetSuggestionChips().empty());
 }
 
-TEST_F(AssistantPageViewTest, ShouldHavePopulatedSuggestionChips) {
+TEST_P(AssistantPageClamshellTest, ShouldHavePopulatedSuggestionChips) {
   constexpr char kAnyQuery[] = "<query>";
   constexpr char kAnyText[] = "<text>";
   constexpr char kAnyChip[] = "<chip>";
@@ -857,7 +884,7 @@
   EXPECT_EQ(kAnyChip, base::UTF16ToUTF8(chip->GetText()));
 }
 
-TEST_F(AssistantPageViewTest, Theme) {
+TEST_P(AssistantPageClamshellTest, Theme) {
   ASSERT_FALSE(features::IsDarkLightModeEnabled());
 
   ShowAssistantUi();
@@ -865,7 +892,7 @@
   EXPECT_EQ(page_view()->background()->get_color(), SK_ColorWHITE);
 }
 
-TEST_F(AssistantPageViewTest, ThemeDarkLightMode) {
+TEST_P(AssistantPageClamshellTest, ThemeDarkLightMode) {
   base::test::ScopedFeatureList scoped_feature_list(features::kDarkLightMode);
   AshColorProvider::Get()->OnActiveUserPrefServiceChanged(
       Shell::Get()->session_controller()->GetActivePrefService());
@@ -886,6 +913,7 @@
                 /*is_dark_mode=*/true, /*use_debug_colors=*/false));
 }
 
+//------------------------------------------------------------------------------
 // Tests the |AssistantPageView| with tablet mode enabled.
 class AssistantPageViewTabletModeTest : public AssistantPageViewTest {
  public:
@@ -910,6 +938,25 @@
     TapOnAndWait(keyboard_input_toggle());
   }
 
+  // Returns a point in the AppList, but outside the Assistant UI.
+  gfx::Point GetPointInAppListOutsideAssistantUi() {
+    gfx::Point result = GetPointOutside(page_view());
+
+    // Validity check
+    EXPECT_TRUE(app_list_view()->bounds().Contains(result));
+    EXPECT_FALSE(page_view()->bounds().Contains(result));
+
+    return result;
+  }
+
+  gfx::Point GetPointOutside(const views::View* view) {
+    return gfx::Point(view->origin().x() - 10, view->origin().y() - 10);
+  }
+
+  gfx::Point GetPointInside(const views::View* view) {
+    return view->GetBoundsInScreen().CenterPoint();
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(AssistantPageViewTabletModeTest);
 };
@@ -1132,4 +1179,5 @@
   EXPECT_FALSE(IsVisible());
 }
 
+}  // namespace
 }  // namespace ash
diff --git a/ash/app_list/views/assistant/assistant_test_api_impl.cc b/ash/app_list/views/assistant/assistant_test_api_impl.cc
index 3617958..1a1df693 100644
--- a/ash/app_list/views/assistant/assistant_test_api_impl.cc
+++ b/ash/app_list/views/assistant/assistant_test_api_impl.cc
@@ -4,13 +4,17 @@
 
 #include "ash/app_list/views/assistant/assistant_test_api_impl.h"
 
+#include "ash/app_list/app_list_bubble_presenter.h"
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/app_list/app_list_presenter_impl.h"
+#include "ash/app_list/bubble/app_list_bubble_assistant_page.h"
+#include "ash/app_list/bubble/app_list_bubble_view.h"
 #include "ash/app_list/views/app_list_main_view.h"
 #include "ash/app_list/views/app_list_page.h"
 #include "ash/app_list/views/app_list_view.h"
 #include "ash/app_list/views/contents_view.h"
 #include "ash/assistant/ui/assistant_view_ids.h"
+#include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "ash/public/cpp/assistant/assistant_state.h"
 #include "ash/public/cpp/tablet_mode.h"
@@ -25,6 +29,16 @@
 #include "ui/views/controls/textfield/textfield.h"
 
 namespace ash {
+namespace {
+
+AppListBubbleView* GetAppListBubbleView() {
+  return Shell::Get()
+      ->app_list_controller()
+      ->bubble_presenter_for_test()  // IN-TEST
+      ->bubble_view_for_test();      // IN-TEST
+}
+
+}  // namespace
 
 std::unique_ptr<AssistantTestApi> AssistantTestApi::Create() {
   return std::make_unique<AssistantTestApiImpl>();
@@ -45,6 +59,12 @@
 }
 
 bool AssistantTestApiImpl::IsVisible() {
+  if (!TabletMode::Get()->InTabletMode() &&
+      features::IsAppListBubbleEnabled()) {
+    auto* bubble_view = GetAppListBubbleView();
+    // `bubble_view` is null when the bubble launcher is closed.
+    return bubble_view && bubble_view->assistant_page_->GetVisible();
+  }
   return AppListViewsHaveBeenCreated() && page_view()->GetVisible();
 }
 
@@ -60,6 +80,13 @@
 }
 
 views::View* AssistantTestApiImpl::page_view() {
+  if (!TabletMode::Get()->InTabletMode() &&
+      features::IsAppListBubbleEnabled()) {
+    auto* bubble_view = GetAppListBubbleView();
+    DCHECK(bubble_view)
+        << "App list is not showing. Display the assistant UI first.";
+    return bubble_view->assistant_page_;
+  }
   const int index = contents_view()->GetPageIndexForState(
       AppListState::kStateEmbeddedAssistant);
   return static_cast<views::View*>(contents_view()->GetPageView(index));
diff --git a/ash/app_list/views/continue_section_view.cc b/ash/app_list/views/continue_section_view.cc
index 0ee6351a..af83b1a 100644
--- a/ash/app_list/views/continue_section_view.cc
+++ b/ash/app_list/views/continue_section_view.cc
@@ -4,14 +4,18 @@
 
 #include "ash/app_list/views/continue_section_view.h"
 
+#include <algorithm>
 #include <memory>
 #include <string>
 #include <vector>
 
 #include "ash/app_list/app_list_view_delegate.h"
+#include "ash/app_list/model/search/search_model.h"
 #include "ash/app_list/views/continue_task_view.h"
 #include "ash/bubble/bubble_utils.h"
 #include "ash/bubble/simple_grid_layout.h"
+#include "ash/public/cpp/app_list/app_list_config.h"
+#include "ash/public/cpp/app_list/app_list_notifier.h"
 #include "base/check.h"
 #include "base/strings/string_util.h"
 #include "extensions/common/constants.h"
@@ -35,6 +39,40 @@
   return label;
 }
 
+bool IsFileType(AppListSearchResultType type) {
+  return type == AppListSearchResultType::kFileChip ||
+         type == AppListSearchResultType::kDriveChip;
+}
+
+struct CompareByDisplayIndexAndPositionPriority {
+  bool operator()(const SearchResult* result1,
+                  const SearchResult* result2) const {
+    SearchResultDisplayIndex index1 = result1->display_index();
+    SearchResultDisplayIndex index2 = result2->display_index();
+    if (index1 != index2)
+      return index1 < index2;
+    return result1->position_priority() > result2->position_priority();
+  }
+};
+
+std::vector<SearchResult*> GetTasksResultsFromSuggestionChips(
+    SearchModel* search_model) {
+  SearchModel::SearchResults* results = search_model->results();
+  auto file_chips_filter = [](const SearchResult& r) -> bool {
+    return IsFileType(r.result_type()) &&
+           r.display_type() == SearchResultDisplayType::kChip;
+  };
+  std::vector<SearchResult*> file_chips_results =
+      SearchModel::FilterSearchResultsByFunction(
+          results, base::BindRepeating(file_chips_filter),
+          /*max_results=*/4);
+
+  std::sort(file_chips_results.begin(), file_chips_results.end(),
+            CompareByDisplayIndexAndPositionPriority());
+
+  return file_chips_results;
+}
+
 }  // namespace
 
 ContinueSectionView::ContinueSectionView(AppListViewDelegate* view_delegate)
@@ -51,18 +89,33 @@
   auto* continue_label = AddChildView(CreateLabel(u"Label"));
   continue_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
 
-  continue_suggestions_ = AddChildView(std::make_unique<views::View>());
-  continue_suggestions_->SetLayoutManager(std::make_unique<SimpleGridLayout>(
+  suggestions_container_ = AddChildView(std::make_unique<views::View>());
+  suggestions_container_->SetLayoutManager(std::make_unique<SimpleGridLayout>(
       kContinueColumnCount, kContinueColumnSpacing, kContinueRowSpacing));
 
-  for (int i = 0; i < 4; ++i) {
-    continue_suggestions_->AddChildView(
-        std::make_unique<ContinueTaskView>(u"Item"));
+  std::vector<SearchResult*> tasks =
+      GetTasksResultsFromSuggestionChips(view_delegate_->GetSearchModel());
+
+  for (SearchResult* task : tasks) {
+    suggestions_container_->AddChildView(
+        std::make_unique<ContinueTaskView>(task));
   }
+  SetVisible(!tasks.empty());
 }
 
 ContinueSectionView::~ContinueSectionView() = default;
 
+size_t ContinueSectionView::GetTasksSuggestionsCount() const {
+  return suggestions_container_->children().size();
+}
+
+ContinueTaskView* ContinueSectionView::GetTaskViewAtForTesting(
+    size_t index) const {
+  DCHECK_GT(GetTasksSuggestionsCount(), index);
+  return static_cast<ContinueTaskView*>(
+      suggestions_container_->children()[index]);
+}
+
 BEGIN_METADATA(ContinueSectionView, views::View)
 END_METADATA
 
diff --git a/ash/app_list/views/continue_section_view.h b/ash/app_list/views/continue_section_view.h
index 709c3a7..11b89d27 100644
--- a/ash/app_list/views/continue_section_view.h
+++ b/ash/app_list/views/continue_section_view.h
@@ -11,6 +11,7 @@
 namespace ash {
 
 class AppListViewDelegate;
+class ContinueTaskView;
 
 // The "Continue" section of the bubble launcher. This view wraps around
 // suggestions with tasks to continue.
@@ -23,10 +24,13 @@
   ContinueSectionView& operator=(const ContinueSectionView&) = delete;
   ~ContinueSectionView() override;
 
+  size_t GetTasksSuggestionsCount() const;
+  ContinueTaskView* GetTaskViewAtForTesting(size_t index) const;
+
  private:
   AppListViewDelegate* const view_delegate_;
 
-  views::View* continue_suggestions_ = nullptr;
+  views::View* suggestions_container_ = nullptr;
 };
 
 }  // namespace ash
diff --git a/ash/app_list/views/continue_section_view_unittest.cc b/ash/app_list/views/continue_section_view_unittest.cc
new file mode 100644
index 0000000..f23a0f9
--- /dev/null
+++ b/ash/app_list/views/continue_section_view_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/app_list/views/continue_section_view.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "ash/app_list/app_list_controller_impl.h"
+#include "ash/app_list/model/search/search_model.h"
+#include "ash/app_list/model/search/test_search_result.h"
+#include "ash/app_list/test/app_list_test_helper.h"
+#include "ash/app_list/views/continue_task_view.h"
+#include "ash/constants/ash_features.h"
+#include "ash/public/cpp/app_list/app_list_types.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "base/test/scoped_feature_list.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+namespace {
+
+void AddSearchResult(const std::string& id, AppListSearchResultType type) {
+  auto result = std::make_unique<TestSearchResult>();
+  result->set_result_id(id);
+  result->set_result_type(type);
+  // TODO(crbug.com/1216662): Replace with a real display type after the ML team
+  // gives us a way to query directly for recent apps.
+  result->set_display_type(SearchResultDisplayType::kChip);
+  Shell::Get()->app_list_controller()->GetSearchModel()->results()->Add(
+      std::move(result));
+}
+
+void ShowAppList() {
+  Shell::Get()->app_list_controller()->ShowAppList();
+}
+
+class ContinueSectionViewTest : public AshTestBase {
+ public:
+  ContinueSectionViewTest() {
+    scoped_feature_list_.InitAndEnableFeature(features::kAppListBubble);
+  }
+  ~ContinueSectionViewTest() override = default;
+
+  // testing::Test:
+  void SetUp() override { AshTestBase::SetUp(); }
+
+  ContinueSectionView* GetContinueSectionView() {
+    return GetAppListTestHelper()->GetContinueSectionView();
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(ContinueSectionViewTest, CreatesViewsForTasks) {
+  AddSearchResult("id1", AppListSearchResultType::kFileChip);
+  AddSearchResult("id2", AppListSearchResultType::kDriveChip);
+
+  ShowAppList();
+
+  ContinueSectionView* view = GetContinueSectionView();
+  EXPECT_EQ(view->GetTasksSuggestionsCount(), 2u);
+}
+
+TEST_F(ContinueSectionViewTest, DoesNotCreateViewsForNonTasks) {
+  AddSearchResult("id1", AppListSearchResultType::kInstalledApp);
+  AddSearchResult("id2", AppListSearchResultType::kPlayStoreApp);
+  AddSearchResult("id3", AppListSearchResultType::kInstantApp);
+  AddSearchResult("id4", AppListSearchResultType::kInternalApp);
+  AddSearchResult("id5", AppListSearchResultType::kAnswerCard);
+  AddSearchResult("id6", AppListSearchResultType::kAssistantText);
+
+  ShowAppList();
+
+  ContinueSectionView* view = GetContinueSectionView();
+  EXPECT_EQ(view->GetTasksSuggestionsCount(), 0u);
+}
+
+TEST_F(ContinueSectionViewTest, VerifyAddedViewsOrder) {
+  AddSearchResult("id1", AppListSearchResultType::kFileChip);
+  AddSearchResult("id2", AppListSearchResultType::kDriveChip);
+  AddSearchResult("id3", AppListSearchResultType::kDriveChip);
+
+  ShowAppList();
+
+  ContinueSectionView* view = GetContinueSectionView();
+  ASSERT_EQ(view->GetTasksSuggestionsCount(), 3u);
+  EXPECT_EQ(view->GetTaskViewAtForTesting(0)->result()->id(), "id1");
+  EXPECT_EQ(view->GetTaskViewAtForTesting(1)->result()->id(), "id2");
+  EXPECT_EQ(view->GetTaskViewAtForTesting(2)->result()->id(), "id3");
+}
+
+}  // namespace
+}  // namespace ash
diff --git a/ash/app_list/views/continue_task_view.cc b/ash/app_list/views/continue_task_view.cc
index e7f94b2..dc1be4d 100644
--- a/ash/app_list/views/continue_task_view.cc
+++ b/ash/app_list/views/continue_task_view.cc
@@ -4,10 +4,12 @@
 
 #include "ash/app_list/views/continue_task_view.h"
 
+#include <algorithm>
 #include <memory>
 #include <string>
 #include <utility>
 
+#include "ash/app_list/model/search/search_result.h"
 #include "ash/bubble/bubble_utils.h"
 #include "base/strings/string_util.h"
 #include "extensions/common/constants.h"
@@ -17,14 +19,26 @@
 
 namespace ash {
 
-ContinueTaskView::ContinueTaskView(const std::u16string& task_title) {
+ContinueTaskView::ContinueTaskView(SearchResult* task_result)
+    : result_(task_result) {
   SetLayoutManager(std::make_unique<views::FillLayout>());
-  auto* label = AddChildView(std::make_unique<views::Label>(task_title));
-  bubble_utils::ApplyStyle(label, bubble_utils::LabelStyle::kBody);
+  title_ = AddChildView(std::make_unique<views::Label>());
+  SetText(result()->title());
+  title_->SetAccessibleName(result()->accessible_name());
 }
 
 ContinueTaskView::~ContinueTaskView() = default;
 
+void ContinueTaskView::SetText(const std::u16string& text) {
+  title_->SetText(text);
+  PreferredSizeChanged();
+}
+
+void ContinueTaskView::OnThemeChanged() {
+  views::View::OnThemeChanged();
+  bubble_utils::ApplyStyle(title_, bubble_utils::LabelStyle::kBody);
+}
+
 BEGIN_METADATA(ContinueTaskView, views::View)
 END_METADATA
 
diff --git a/ash/app_list/views/continue_task_view.h b/ash/app_list/views/continue_task_view.h
index 79c217c..555d231 100644
--- a/ash/app_list/views/continue_task_view.h
+++ b/ash/app_list/views/continue_task_view.h
@@ -11,17 +11,33 @@
 #include "ash/ash_export.h"
 #include "ui/views/view.h"
 
+namespace views {
+class Label;
+}
+
 namespace ash {
+class SearchResult;
 
 // A view with a suggested task for the "Continue" section.
 class ASH_EXPORT ContinueTaskView : public views::View {
  public:
   METADATA_HEADER(ContinueTaskView);
 
-  explicit ContinueTaskView(const std::u16string& task_title);
+  explicit ContinueTaskView(SearchResult* result);
   ContinueTaskView(const ContinueTaskView&) = delete;
   ContinueTaskView& operator=(const ContinueTaskView&) = delete;
   ~ContinueTaskView() override;
+
+  // views::View:
+  void OnThemeChanged() override;
+
+  SearchResult* result() { return result_; }
+
+ private:
+  void SetText(const std::u16string& task_title);
+
+  SearchResult* result_;
+  views::Label* title_;
 };
 
 }  // namespace ash
diff --git a/ash/assistant/test/assistant_ash_test_base.cc b/ash/assistant/test/assistant_ash_test_base.cc
index 723ab4c..74fd1e75 100644
--- a/ash/assistant/test/assistant_ash_test_base.cc
+++ b/ash/assistant/test/assistant_ash_test_base.cc
@@ -21,6 +21,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_helper.h"
+#include "ash/test/view_drawn_waiter.h"
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
 #include "chromeos/services/assistant/test_support/scoped_assistant_browser_delegate.h"
@@ -164,6 +165,8 @@
   }
   // Send all mojom messages to/from the assistant service.
   base::RunLoop().RunUntilIdle();
+  // Ensure assistant page is visible and has finished layout to non-zero size.
+  ViewDrawnWaiter().Wait(page_view());
 }
 
 void AssistantAshTestBase::CloseAssistantUi(AssistantExitPoint exit_point) {
diff --git a/ash/capture_mode/capture_mode_unittests.cc b/ash/capture_mode/capture_mode_unittests.cc
index de3b823..d0a79d4 100644
--- a/ash/capture_mode/capture_mode_unittests.cc
+++ b/ash/capture_mode/capture_mode_unittests.cc
@@ -3214,14 +3214,14 @@
   auto* clipboard = ui::Clipboard::GetForCurrentThread();
   ASSERT_NE(clipboard, nullptr);
 
-  const uint64_t before_sequence_number =
+  const ui::ClipboardSequenceNumberToken before_sequence_number =
       clipboard->GetSequenceNumber(ui::ClipboardBuffer::kCopyPaste);
 
   CaptureNotificationWaiter waiter;
   CaptureModeController::Get()->CaptureScreenshotsOfAllDisplays();
   waiter.Wait();
 
-  const uint64_t after_sequence_number =
+  const ui::ClipboardSequenceNumberToken after_sequence_number =
       clipboard->GetSequenceNumber(ui::ClipboardBuffer::kCopyPaste);
 
   EXPECT_NE(before_sequence_number, after_sequence_number);
diff --git a/ash/clipboard/clipboard_history_unittest.cc b/ash/clipboard/clipboard_history_unittest.cc
index 14d21e9..7707c7d 100644
--- a/ash/clipboard/clipboard_history_unittest.cc
+++ b/ash/clipboard/clipboard_history_unittest.cc
@@ -120,7 +120,7 @@
     {
       ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
       scw.WritePickledData(input_data_pickle,
-                           ui::ClipboardFormatType::GetWebCustomDataType());
+                           ui::ClipboardFormatType::WebCustomDataType());
     }
     base::RunLoop().RunUntilIdle();
 
diff --git a/ash/clipboard/test_support/clipboard_history_item_builder.cc b/ash/clipboard/test_support/clipboard_history_item_builder.cc
index 782ac122..ebee433 100644
--- a/ash/clipboard/test_support/clipboard_history_item_builder.cc
+++ b/ash/clipboard/test_support/clipboard_history_item_builder.cc
@@ -214,10 +214,9 @@
           {{kFileSystemSourcesType, base::JoinString(source_list, u"\n")}}),
       &custom_data);
 
-  return SetCustomData(
-      ui::ClipboardFormatType::GetWebCustomDataType().GetName(),
-      std::string(static_cast<const char*>(custom_data.data()),
-                  custom_data.size()));
+  return SetCustomData(ui::ClipboardFormatType::WebCustomDataType().GetName(),
+                       std::string(static_cast<const char*>(custom_data.data()),
+                                   custom_data.size()));
 }
 
 ClipboardHistoryItemBuilder& ClipboardHistoryItemBuilder::SetWebSmartPaste(
diff --git a/ash/components/audio/audio_devices_pref_handler_impl.cc b/ash/components/audio/audio_devices_pref_handler_impl.cc
index 8386d6d..7888d89 100644
--- a/ash/components/audio/audio_devices_pref_handler_impl.cc
+++ b/ash/components/audio/audio_devices_pref_handler_impl.cc
@@ -405,7 +405,7 @@
   registry->RegisterDictionaryPref(prefs::kAudioDevicesGainPercent);
   registry->RegisterDictionaryPref(prefs::kAudioDevicesMute);
   registry->RegisterDictionaryPref(prefs::kAudioDevicesState);
-  registry->RegisterBooleanPref(prefs::kInputNoiseCancellationEnabled, false);
+  registry->RegisterBooleanPref(prefs::kInputNoiseCancellationEnabled, true);
 
   // Register the prefs backing the audio muting policies.
   // Policy for audio input is handled by kAudioCaptureAllowed in the Chrome
diff --git a/ash/components/audio/audio_devices_pref_handler_impl_unittest.cc b/ash/components/audio/audio_devices_pref_handler_impl_unittest.cc
index b1754897..c89da98 100644
--- a/ash/components/audio/audio_devices_pref_handler_impl_unittest.cc
+++ b/ash/components/audio/audio_devices_pref_handler_impl_unittest.cc
@@ -453,9 +453,9 @@
 }
 
 TEST_P(AudioDevicesPrefHandlerTest, InputNoiseCancellationPrefRegistered) {
-  EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
-  audio_pref_handler_->SetNoiseCancellationState(true);
   EXPECT_TRUE(audio_pref_handler_->GetNoiseCancellationState());
+  audio_pref_handler_->SetNoiseCancellationState(false);
+  EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
 }
 
 }  // namespace ash
diff --git a/ash/components/audio/audio_devices_pref_handler_stub.h b/ash/components/audio/audio_devices_pref_handler_stub.h
index 880d2fc..9b754c0 100644
--- a/ash/components/audio/audio_devices_pref_handler_stub.h
+++ b/ash/components/audio/audio_devices_pref_handler_stub.h
@@ -57,7 +57,7 @@
   AudioDeviceVolumeGain audio_device_volume_gain_map_;
   AudioDeviceStateMap audio_device_state_map_;
 
-  bool noise_cancellation_state_ = false;
+  bool noise_cancellation_state_ = true;
 
   DISALLOW_COPY_AND_ASSIGN(AudioDevicesPrefHandlerStub);
 };
diff --git a/ash/components/audio/cras_audio_handler.cc b/ash/components/audio/cras_audio_handler.cc
index f481bb6..68fb78e 100644
--- a/ash/components/audio/cras_audio_handler.cc
+++ b/ash/components/audio/cras_audio_handler.cc
@@ -1960,7 +1960,6 @@
 }
 
 bool CrasAudioHandler::noise_cancellation_supported() const {
-  DCHECK(main_task_runner_->BelongsToCurrentThread());
   return noise_cancellation_supported_;
 }
 
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index cb734385..3f8e2ad 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -423,7 +423,7 @@
 
 // Enables or disables noise cancellation UI toggle.
 const base::Feature kEnableInputNoiseCancellationUi{
-    "EnableInputNoiseCancellationUi", base::FEATURE_DISABLED_BY_DEFAULT};
+    "EnableInputNoiseCancellationUi", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables LocalSearchService to be initialized.
 const base::Feature kEnableLocalSearchService{"EnableLocalSearchService",
@@ -459,6 +459,11 @@
 const base::Feature kEolWarningNotifications{"EolWarningNotifications",
                                              base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enables or disables enterprise policy control for eSIM cellular networks.
+// See https://crbug.com/1231305.
+const base::Feature kESimPolicy{"ESimPolicy",
+                                base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enable or disable bubble showing when an application gains any UI lock.
 const base::Feature kExoLockNotification{"ExoLockNotification",
                                          base::FEATURE_ENABLED_BY_DEFAULT};
@@ -1292,6 +1297,10 @@
   return base::FeatureList::IsEnabled(kEcheSWAResizing);
 }
 
+bool IsESimPolicyEnabled() {
+  return base::FeatureList::IsEnabled(kESimPolicy);
+}
+
 bool IsFamilyLinkOnSchoolDeviceEnabled() {
   return base::FeatureList::IsEnabled(kFamilyLinkOnSchoolDevice);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index b0c35075..99763a0 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -182,6 +182,7 @@
 extern const base::Feature kEnableSamlReauthenticationOnLockscreen;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kEolWarningNotifications;
+COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kESimPolicy;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kExoLockNotification;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kExoOrdinalMotion;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kExoPointerLock;
diff --git a/ash/drag_drop/drag_drop_controller.cc b/ash/drag_drop/drag_drop_controller.cc
index 9aabdee..ed44b65 100644
--- a/ash/drag_drop/drag_drop_controller.cc
+++ b/ash/drag_drop/drag_drop_controller.cc
@@ -172,10 +172,13 @@
     return DragOperation::kNone;
 
   const ui::OSExchangeDataProvider* provider = &data->provider();
-  // We do not support touch drag/drop without a drag image.
+  // We do not support touch drag/drop without a drag image, unless it is a tab
+  // drag/drop.
   if (source == ui::mojom::DragEventSource::kTouch &&
-      provider->GetDragImage().size().IsEmpty())
+      provider->GetDragImage().size().IsEmpty() &&
+      !toplevel_window_drag_delegate_) {
     return DragOperation::kNone;
+  }
 
   operation_ = DragOperation::kNone;
   current_drag_event_source_ = source;
@@ -383,16 +386,20 @@
   if (current_drag_event_source_ != ui::mojom::DragEventSource::kTouch)
     return;
 
-  // Apply kTouchDragImageVerticalOffset to the location.
+  // Apply kTouchDragImageVerticalOffset to the location, if it is not a tab
+  // drag/drop.
   ui::GestureEvent touch_offset_event(*event,
                                       static_cast<aura::Window*>(nullptr),
                                       static_cast<aura::Window*>(nullptr));
-  gfx::PointF touch_offset_location = touch_offset_event.location_f();
-  gfx::PointF touch_offset_root_location = touch_offset_event.root_location_f();
-  touch_offset_location.Offset(0, kTouchDragImageVerticalOffset);
-  touch_offset_root_location.Offset(0, kTouchDragImageVerticalOffset);
-  touch_offset_event.set_location_f(touch_offset_location);
-  touch_offset_event.set_root_location_f(touch_offset_root_location);
+  if (!toplevel_window_drag_delegate_) {
+    gfx::PointF touch_offset_location = touch_offset_event.location_f();
+    gfx::PointF touch_offset_root_location =
+        touch_offset_event.root_location_f();
+    touch_offset_location.Offset(0, kTouchDragImageVerticalOffset);
+    touch_offset_root_location.Offset(0, kTouchDragImageVerticalOffset);
+    touch_offset_event.set_location_f(touch_offset_location);
+    touch_offset_event.set_root_location_f(touch_offset_root_location);
+  }
 
   aura::Window* translated_target =
       drag_drop_tracker_->GetTarget(touch_offset_event);
diff --git a/ash/drag_drop/drag_drop_controller_unittest.cc b/ash/drag_drop/drag_drop_controller_unittest.cc
index 16b46884..060d3b7 100644
--- a/ash/drag_drop/drag_drop_controller_unittest.cc
+++ b/ash/drag_drop/drag_drop_controller_unittest.cc
@@ -729,7 +729,7 @@
     ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
     scw.WriteText(base::ASCIIToUTF16(clip_str));
   }
-  EXPECT_TRUE(cb->IsFormatAvailable(ui::ClipboardFormatType::GetPlainTextType(),
+  EXPECT_TRUE(cb->IsFormatAvailable(ui::ClipboardFormatType::PlainTextType(),
                                     ui::ClipboardBuffer::kCopyPaste,
                                     /* data_dst = */ nullptr));
 
@@ -747,7 +747,7 @@
 
   // Verify the clipboard contents haven't changed
   std::string result;
-  EXPECT_TRUE(cb->IsFormatAvailable(ui::ClipboardFormatType::GetPlainTextType(),
+  EXPECT_TRUE(cb->IsFormatAvailable(ui::ClipboardFormatType::PlainTextType(),
                                     ui::ClipboardBuffer::kCopyPaste,
                                     /* data_dst = */ nullptr));
   cb->ReadAsciiText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
@@ -1503,6 +1503,29 @@
   }
 }
 
+TEST_F(DragDropControllerTest, ToplevelWindowDragDelegateWithTouch) {
+  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
+      aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), -1,
+      gfx::Rect(0, 0, 100, 100)));
+
+  // Emulate a full drag and drop flow and verify that toplevel window drag
+  // delegate gets notified about the events as expected.
+  TestToplevelWindowDragDelegate delegate;
+  drag_drop_controller_->set_toplevel_window_drag_delegate(&delegate);
+
+  auto data(std::make_unique<ui::OSExchangeData>());
+  drag_drop_controller_->StartDragAndDrop(
+      std::move(data), window->GetRootWindow(), window.get(), gfx::Point(5, 5),
+      ui::DragDropTypes::DRAG_MOVE, ui::mojom::DragEventSource::kTouch);
+
+  EXPECT_EQ(TestToplevelWindowDragDelegate::State::kDragStartedInvoked,
+            delegate.state());
+  EXPECT_EQ(ui::mojom::DragEventSource::kTouch, delegate.source());
+  EXPECT_TRUE(delegate.current_location().has_value());
+  EXPECT_EQ(gfx::PointF(5, 5), *delegate.current_location());
+  EXPECT_EQ(0, delegate.events_forwarded());
+}
+
 namespace {
 
 class MockDataTransferPolicyController
diff --git a/ash/hud_display/OWNERS b/ash/hud_display/OWNERS
new file mode 100644
index 0000000..2dcc458
--- /dev/null
+++ b/ash/hud_display/OWNERS
@@ -0,0 +1 @@
+alemate@chromium.org
diff --git a/ash/login/ui/system_label_button.cc b/ash/login/ui/system_label_button.cc
index a5d8278..6a0dd99 100644
--- a/ash/login/ui/system_label_button.cc
+++ b/ash/login/ui/system_label_button.cc
@@ -113,13 +113,12 @@
   SkColor effective_background_color = color_utils::GetResultingPaintColor(
       background_color_,
       AshColorProvider::Get()->GetBaseLayerColor(kBubbleLayerType));
-  const AshColorProvider::RippleAttributes ripple_attributes =
-      AshColorProvider::Get()->GetRippleAttributes(effective_background_color);
-  views::InkDrop::Get(this)->SetBaseColor(ripple_attributes.base_color);
-  views::InkDrop::Get(this)->SetVisibleOpacity(
-      ripple_attributes.inkdrop_opacity);
-  views::InkDrop::Get(this)->SetHighlightOpacity(
-      ripple_attributes.highlight_opacity);
+  AshColorProvider::Get()->DecorateInkDrop(
+      views::InkDrop::Get(this),
+      AshColorProvider::kConfigBaseColor |
+          AshColorProvider::kConfigHighlightOpacity |
+          AshColorProvider::kConfigVisibleOpacity,
+      effective_background_color);
 }
 
 }  // namespace ash
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index b75e8a6..acfdef8 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -290,10 +290,14 @@
     "system_tray.h",
     "system_tray_client.h",
     "system_tray_observer.h",
+    "tab_cluster/correlation_clusterer.cc",
+    "tab_cluster/correlation_clusterer.h",
     "tab_cluster/tab_cluster_ui_controller.cc",
     "tab_cluster/tab_cluster_ui_controller.h",
     "tab_cluster/tab_cluster_ui_item.cc",
     "tab_cluster/tab_cluster_ui_item.h",
+    "tab_cluster/undirected_graph.cc",
+    "tab_cluster/undirected_graph.h",
     "tablet_mode.cc",
     "tablet_mode.h",
     "tablet_mode_observer.h",
@@ -402,6 +406,8 @@
     "pagination/pagination_model_unittest.cc",
     "power_utils_unittest.cc",
     "shelf_model_unittest.cc",
+    "tab_cluster/correlation_clusterer_unittest.cc",
+    "tab_cluster/undirected_graph_unittest.cc",
     "view_shadow_unittest.cc",
   ]
 
diff --git a/ash/public/cpp/tab_cluster/OWNERS b/ash/public/cpp/tab_cluster/OWNERS
index f5aa337..e34d8d6 100644
--- a/ash/public/cpp/tab_cluster/OWNERS
+++ b/ash/public/cpp/tab_cluster/OWNERS
@@ -1 +1,3 @@
 zxdan@chromium.org
+tby@chromium.org
+myy@chromium.org
diff --git a/ash/public/cpp/tab_cluster/correlation_clusterer.cc b/ash/public/cpp/tab_cluster/correlation_clusterer.cc
new file mode 100644
index 0000000..f71f9ec6
--- /dev/null
+++ b/ash/public/cpp/tab_cluster/correlation_clusterer.cc
@@ -0,0 +1,309 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/public/cpp/tab_cluster/correlation_clusterer.h"
+
+#include <map>
+#include <set>
+
+#include "ash/public/cpp/tab_cluster/undirected_graph.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace ash {
+
+namespace {
+
+// Number of times we run the clustering algorithm on the given graph.
+// This is an arbitrary number and might be subjected to further tuning.
+constexpr int kNumIterations = 10;
+
+// Converts current clustering into vector of vectors format.
+std::vector<std::vector<int>> OutputClusters(
+    const std::vector<int>& clustering) {
+  std::map<int, std::vector<int>> clusters;
+  for (size_t i = 0; i < clustering.size(); ++i) {
+    clusters[clustering[i]].push_back(i);
+  }
+  std::vector<std::vector<int>> output;
+  for (auto& key_value : clusters) {
+    auto& cluster = key_value.second;
+    output.push_back(std::move(cluster));
+  }
+  return output;
+}
+}  // namespace
+
+// A helper class that keeps track of the sum of edge weights, accounting
+// for missing edges, for best move computations.
+class EdgeSum {
+ public:
+  EdgeSum() = default;
+  EdgeSum(const EdgeSum&) = delete;
+  EdgeSum& operator=(const EdgeSum&) = delete;
+  ~EdgeSum() = default;
+
+  // The edge weight `w` should have the edge weight offset subtracted before
+  // calling this function.
+  void Add(double w) { weight_ += w; }
+  // Should be called at most once, after all edges have been Add()ed.
+  void RemoveDoubleCounting() { weight_ /= 2.0; }
+  // Retrieve the total weight of all edges seen, correcting for the implicit
+  // negative weight of resolution multiplied by the product of the weights of
+  // the two nodes incident to each edge.
+  double NetWeight(
+      double sum_prod_node_weights,
+      const CorrelationClusterer::CorrelationClustererConfig& config) const {
+    return weight_ - config.resolution * sum_prod_node_weights;
+  }
+
+ private:
+  double weight_ = 0.0;
+};
+
+CorrelationClusterer::CorrelationClusterer() = default;
+CorrelationClusterer::~CorrelationClusterer() = default;
+
+std::vector<std::vector<int>> CorrelationClusterer::Cluster(
+    const UndirectedGraph& undirected_graph) {
+  Reset();
+
+  graph_ = undirected_graph;
+  num_nodes_ = graph_.NumNodes();
+
+  // Create all-singletons initial clusters
+  std::vector<std::vector<int>> clusters;
+
+  for (int i = 0; i < num_nodes_; ++i) {
+    clusters.push_back({i});
+  }
+
+  // Initialize to all-singletons clustering.
+  clustering_.reserve(num_nodes_);
+  for (int i = 0; i < num_nodes_; ++i) {
+    int cluster = NewClusterId();
+    clustering_.push_back(cluster);
+    cluster_sizes_[cluster] = 1;
+    cluster_weights_[cluster] = graph_.NodeWeight(i);
+  }
+
+  // Modularity objective.
+  config_.resolution = 1.0 / graph_.total_node_weight();
+
+  RefineClusters(&clusters);
+
+  return clusters;
+}
+
+void CorrelationClusterer::RefineClusters(
+    std::vector<std::vector<int>>* clusters_ptr) {
+  std::string error;
+  SetClustering(*clusters_ptr, &error);
+  if (!error.empty()) {
+    LOG(ERROR) << "Failed to set clustering " << error;
+    return;
+  }
+
+  double objective = 0;
+
+  auto try_moves = [&](std::vector<std::set<int>>* clusters_to_try) {
+    base::RandomShuffle(clusters_to_try->begin(), clusters_to_try->end());
+    for (const auto& cluster : *clusters_to_try) {
+      std::pair<absl::optional<int>, double> best_move = BestMove(cluster);
+      if (best_move.second > 0) {
+        absl::optional<int> new_cluster = best_move.first;
+        MoveNodesToCluster(cluster, new_cluster);
+        objective += best_move.second;
+      }
+    }
+  };
+
+  for (int iter = 0; iter < kNumIterations; ++iter) {
+    // Use current clusters as move sets, which means we'll consider
+    // merging clusters.
+    std::map<int, std::set<int>> node_cluster_map;
+    for (int i = 0; i < num_nodes_; ++i) {
+      node_cluster_map[ClusterForNode(i)].insert(i);
+    }
+    std::vector<std::set<int>> temp_clusters;
+    for (auto& key_value : node_cluster_map) {
+      auto& cluster = key_value.second;
+      temp_clusters.push_back(std::move(cluster));
+    }
+    try_moves(&temp_clusters);
+  }
+
+  *clusters_ptr = OutputClusters(clustering_);
+}
+
+bool CorrelationClusterer::SetClustering(
+    const std::vector<std::vector<int>>& clusters,
+    std::string* error) {
+  std::vector<bool> seen_nodes(num_nodes_);
+  for (const auto& cluster : clusters) {
+    int id = NewClusterId();
+    for (const auto node : cluster) {
+      if (node >= num_nodes_ || node < 0) {
+        *error =
+            base::StrCat({"Node id ", base::NumberToString(node),
+                          " in initial clusters not in expected range [0, ",
+                          base::NumberToString(num_nodes_), ")"});
+        return false;
+      }
+      if (seen_nodes[node]) {
+        *error = base::StrCat({"Node id ", base::NumberToString(node),
+                               " appears in initial clusters more than once."});
+        return false;
+      }
+      seen_nodes[node] = true;
+      MoveNodeToCluster(node, id);
+    }
+  }
+  for (int node = 0; node < num_nodes_; ++node) {
+    if (!seen_nodes[node]) {
+      *error = base::StrCat({"Node id ", base::NumberToString(node),
+                             " does not appear in initial clusters."});
+      return false;
+    }
+  }
+  return true;
+}
+
+void CorrelationClusterer::MoveNodeToCluster(const int node,
+                                             const int new_cluster) {
+  const int old_cluster = clustering_[node];
+  const double weight = graph_.NodeWeight(node);
+  cluster_sizes_[old_cluster] -= 1;
+  cluster_weights_[old_cluster] -= weight;
+  if (cluster_sizes_[old_cluster] == 0) {
+    DCHECK_EQ(static_cast<int>(cluster_sizes_.erase(old_cluster)), 1);
+    DCHECK_EQ(static_cast<int>(cluster_weights_.erase(old_cluster)), 1);
+  }
+  clustering_[node] = new_cluster;
+  cluster_sizes_[new_cluster] += 1;
+  cluster_weights_[new_cluster] += weight;
+}
+
+// Null optional means make a new cluster.
+void CorrelationClusterer::MoveNodesToCluster(const std::set<int>& nodes,
+                                              absl::optional<int> new_cluster) {
+  int actual_new_cluster = new_cluster ? *new_cluster : NewClusterId();
+  for (const auto& node : nodes) {
+    MoveNodeToCluster(node, actual_new_cluster);
+  }
+}
+
+std::pair<absl::optional<int>, double> CorrelationClusterer::BestMove(
+    const std::set<int>& moving_nodes) {
+  // Weight of nodes in each cluster that are moving.
+  std::map<int, double> cluster_moving_weights;
+  // Class 2 edges where the endpoints are currently in different clusters.
+  EdgeSum class_2_currently_separate;
+  // Class 1 edges where the endpoints are currently in the same cluster.
+  EdgeSum class_1_currently_together;
+  // Class 1 edges, grouped by the cluster that the non-moving node is in.
+  std::map<int, EdgeSum> class_1_together_after;
+
+  double moving_nodes_weight = 0;
+  for (const auto& node : moving_nodes) {
+    const int node_cluster = clustering_[node];
+    cluster_moving_weights[node_cluster] += graph_.NodeWeight(node);
+    moving_nodes_weight += graph_.NodeWeight(node);
+    for (const auto& edge : graph_.Neighbors(node)) {
+      const auto neighbor = edge.first;
+      const auto weight = edge.second;
+      const int neighbor_cluster = clustering_[neighbor];
+      if (moving_nodes.find(neighbor) != moving_nodes.end()) {
+        // Class 2 edge.
+        if (node_cluster != neighbor_cluster) {
+          class_2_currently_separate.Add(weight);
+        }
+      } else {
+        // Class 1 edge.
+        if (node_cluster == neighbor_cluster) {
+          class_1_currently_together.Add(weight);
+        }
+        class_1_together_after[neighbor_cluster].Add(weight);
+      }
+    }
+  }
+  class_2_currently_separate.RemoveDoubleCounting();
+  // Now cluster_moving_weights is correct and class_2_currently_separate,
+  // class_1_currently_together, and class_1_by_cluster are ready to call
+  // NetWeight().
+
+  return BestMoveFromStats(moving_nodes_weight, cluster_moving_weights,
+                           class_2_currently_separate,
+                           class_1_currently_together, class_1_together_after);
+}
+
+std::pair<absl::optional<int>, double> CorrelationClusterer::BestMoveFromStats(
+    double moving_nodes_weight,
+    std::map<int, double>& cluster_moving_weights,
+    const EdgeSum& class_2_currently_separate,
+    const EdgeSum& class_1_currently_together,
+    const std::map<int, EdgeSum>& class_1_together_after) {
+  double change_in_objective = 0.0;
+
+  auto half_square = [](double x) { return x * x / 2.0; };
+  double max_edges = half_square(moving_nodes_weight);
+  for (const auto& cluster_moving_weight : cluster_moving_weights) {
+    max_edges -= half_square(cluster_moving_weight.second);
+  }
+  change_in_objective +=
+      class_2_currently_separate.NetWeight(max_edges, config_);
+
+  max_edges = 0;
+  for (const auto& cluster_moving_weights : cluster_moving_weights) {
+    max_edges +=
+        moving_nodes_weight * (GetClusterWeight(cluster_moving_weights.first) -
+                               cluster_moving_weights.second);
+  }
+  change_in_objective -=
+      class_1_currently_together.NetWeight(max_edges, config_);
+
+  std::pair<absl::optional<int>, double> best_move;
+  best_move.first = absl::nullopt;
+  best_move.second = change_in_objective;
+  for (const auto& cluster_data : class_1_together_after) {
+    int cluster = cluster_data.first;
+    const EdgeSum& data = cluster_data.second;
+    max_edges = moving_nodes_weight *
+                (GetClusterWeight(cluster) - cluster_moving_weights[cluster]);
+    // Change in objective if we move the moving nodes to cluster i.
+    double overall_change_in_objective =
+        change_in_objective + data.NetWeight(max_edges, config_);
+    if (overall_change_in_objective > best_move.second ||
+        (overall_change_in_objective == best_move.second &&
+         cluster < best_move.first)) {
+      best_move.first = cluster;
+      best_move.second = overall_change_in_objective;
+    }
+  }
+  return best_move;
+}
+
+int CorrelationClusterer::NewClusterId() {
+  return next_cluster_id_++;
+}
+
+int CorrelationClusterer::ClusterForNode(int node) const {
+  return clustering_[node];
+}
+
+double CorrelationClusterer::GetClusterWeight(int cluster_id) const {
+  return cluster_weights_.at(cluster_id);
+}
+
+void CorrelationClusterer::Reset() {
+  clustering_.clear();
+  cluster_sizes_.clear();
+  cluster_weights_.clear();
+  next_cluster_id_ = 0;
+}
+
+}  // namespace ash
diff --git a/ash/public/cpp/tab_cluster/correlation_clusterer.h b/ash/public/cpp/tab_cluster/correlation_clusterer.h
new file mode 100644
index 0000000..e9010ee
--- /dev/null
+++ b/ash/public/cpp/tab_cluster/correlation_clusterer.h
@@ -0,0 +1,148 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_PUBLIC_CPP_TAB_CLUSTER_CORRELATION_CLUSTERER_H_
+#define ASH_PUBLIC_CPP_TAB_CLUSTER_CORRELATION_CLUSTERER_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "ash/public/cpp/ash_public_export.h"
+#include "ash/public/cpp/tab_cluster/undirected_graph.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace ash {
+
+class UndirectedGraph;
+class EdgeSum;
+
+// Adapted from
+// https://github.com/google-research/google-research/blob/master/parallel_clustering/clustering/config.proto#L66
+// Consider a graph with vertex set V, edge set E, non-negative vertex weights
+// k_u, edge weights w_uv, and a "resolution" parameter which must be
+// non-negative. We define "rescaled" edge weights w'_uv for all u, v, in V as:
+//             { 0                                if u == v
+// w'_{uv} =   {  w_uv  -  resolution k_u k_v     if {u, v} in E,
+//             {  -resolution k_u k_v             otherwise
+// The correlation clustering objective is to maximize
+//   sum_{u, v in the same cluster} w'_uv,
+// which is equivalent (up to sign and an additive constant) to the
+// "maximizing agreements" and "minimizing disagreements" formulations of
+// correlation clustering that are used in the approximation algorithms
+// literature. Assuming the total edge weight in the graph is M, modularity
+// partitioning can be expressed in this form by:
+//  * setting resolution = 1/(2*M),
+//  * setting the weight of each node to the total weight of its incident edges.
+// Note that the final correlation clustering objective is monotonic in, but not
+// equal to modularity. In particular, if the correlation clustering objective
+// is C, we have:
+// modularity = (C - resolution * sum_v (deg_v)^2 / (4 * M)) / M.
+//
+// To optimize this objective we use local search. We start with each vertex in
+// its own cluster. We consider moves of the following form: move all vertices
+// in a "move set" S of vertices to either one of the existing clusters or to a
+// newly created cluster. We currently consider the following options for S:
+//  - One move set per current cluster with all the vertices currently in it.
+//    With these move sets we're effectively considering merging two clusters.
+// For each move set considered we move that move set to the cluster that
+// improves the objective the most if an improving move exists.
+// TODO(crbug/1231303): //ash/public mostly contains interfaces, shared
+// constants, types and utility classes, which are used to share code between
+// //ash and //chrome. It's not common to put all implementation inside
+// //ash/public. Find a better place to move this class to.
+class ASH_PUBLIC_EXPORT CorrelationClusterer {
+ public:
+  struct CorrelationClustererConfig {
+    double resolution = 1.0;
+  };
+  CorrelationClusterer();
+  CorrelationClusterer(const CorrelationClusterer&) = delete;
+  CorrelationClusterer& operator=(const CorrelationClusterer&) = delete;
+  ~CorrelationClusterer();
+
+  // Returns a list of clusters for a given undirected graph.
+  std::vector<std::vector<int>> Cluster(
+      const UndirectedGraph& undirected_graph);
+
+ private:
+  // The main clustering function.
+  // initial_clustering must include every node in the range
+  // [0, MutableGraph().NumNodes()) exactly once. If it doesn't this function
+  // will log an error message and return clusters of singleton.
+  void RefineClusters(std::vector<std::vector<int>>* clusters_ptr);
+  // Fills `clustering_` with a map of node (index) to cluster id (value)
+  // from the given set of `clusters`, returns false with non-empty
+  // error string when a problem occurred.
+  bool SetClustering(const std::vector<std::vector<int>>& clusters,
+                     std::string* error);
+  // Moves node from its current cluster to a new cluster.
+  void MoveNodeToCluster(const int node, const int new_cluster);
+  void MoveNodesToCluster(const std::set<int>& nodes,
+                          absl::optional<int> new_cluster);
+  // Returns a pair of:
+  //  * The best cluster to move `moving_nodes` to according to the correlation
+  //    clustering objective function. Null optional means create a new cluster.
+  //  * The change in objective function achieved by that move. May be positive
+  //    or negative.
+  // The runtime is linear in the number of edges incident to `moving_nodes`.
+  std::pair<absl::optional<int>, double> BestMove(
+      const std::set<int>& moving_nodes);
+  // Computes the best move given certain pre-computed sums of edge weights of
+  // the following classes of vertices in relation to a fixed set of
+  // `moving_nodes` that may change clusters:
+  //  * Class 0: Neither node is moving.
+  //  * Class 1: Exactly one node is moving.
+  //  * Class 2: Both nodes are moving.
+  // where "moving" means in moving_nodes.
+  //
+  // Change in objective if we move all `moving nodes` to cluster i:
+  //   class_2_currently_separate + class_1_together_after[i] -
+  //   class_1_currently_together
+  // where
+  //   class_2_currently_separate = Weight of edges in class 2 where the
+  //   endpoints are in different clusters currently
+  //   class_1_together_after[i] = Weight of edges in class 1 where the
+  //   non-moving node is in cluster i
+  //   class_1_currently_together = Weight of edges in class 1 where the
+  //   endpoints are in the same cluster currently
+  //
+  // Two complications:
+  //   * We need to avoid double-counting pairs in class 2
+  //   * We need to account for missing edges, which have weight
+  //     -resolution. To do so we subtract the number of edges we see in each
+  //     category from the max possible number of edges (i.e. the number of
+  //     edges we'd have if the graph was complete).
+  std::pair<absl::optional<int>, double> BestMoveFromStats(
+      double moving_nodes_weight,
+      std::map<int, double>& cluster_moving_weights,
+      const EdgeSum& class_2_currently_separate,
+      const EdgeSum& class_1_currently_together,
+      const std::map<int, EdgeSum>& class_1_together_after);
+  // Increments `next_cluster_id_`.
+  int NewClusterId();
+  // Gets the cluster of a given `node` from `clustering_`.
+  int ClusterForNode(int node) const;
+  // Gets cluster weight of a `cluster_id` from `cluster_weights_` map.
+  double GetClusterWeight(int cluster_id) const;
+  // Resets data members before performing clustering.
+  void Reset();
+
+  UndirectedGraph graph_;
+  CorrelationClustererConfig config_;
+  // Maps node to its cluster.
+  std::vector<int> clustering_;
+  // Number of nodes in each cluster.
+  // Currently this is used only to detect when a cluster is empty so its
+  // entry can be removed from cluster_sizes_ and cluster_weights_.
+  std::map<int, int32_t> cluster_sizes_;
+  // Sum of the weights of the nodes in each cluster.
+  std::map<int, double> cluster_weights_;
+  int next_cluster_id_ = 0;
+  int num_nodes_ = 0;
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_TAB_CLUSTER_CORRELATION_CLUSTERER_H_
diff --git a/ash/public/cpp/tab_cluster/correlation_clusterer_unittest.cc b/ash/public/cpp/tab_cluster/correlation_clusterer_unittest.cc
new file mode 100644
index 0000000..dfa01dd
--- /dev/null
+++ b/ash/public/cpp/tab_cluster/correlation_clusterer_unittest.cc
@@ -0,0 +1,49 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/public/cpp/tab_cluster/correlation_clusterer.h"
+
+#include "ash/public/cpp/tab_cluster/undirected_graph.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+using ::testing::UnorderedElementsAreArray;
+
+// TODO(b/190004922): Update the algorithm so that a fully connected graph
+// with all edge weights = 1 should only produce one cluster.
+
+TEST(CorrelationClusterer, TwoDistinctClusters) {
+  CorrelationClusterer clusterer;
+  UndirectedGraph graph;
+  graph.AddUndirectedEdgeAndNodeWeight(0, 1);
+  graph.AddUndirectedEdgeAndNodeWeight(1, 0);
+  graph.AddUndirectedEdgeAndNodeWeight(0, 2);
+  graph.AddUndirectedEdgeAndNodeWeight(2, 3);
+  graph.AddUndirectedEdgeAndNodeWeight(3, 2);
+  std::vector<std::vector<int>> clusters = clusterer.Cluster(graph);
+  EXPECT_THAT(clusters,
+              UnorderedElementsAreArray<std::vector<int>>({{0, 1}, {2, 3}}));
+}
+
+TEST(CorrelationClusterer, EmptyClusterWhenGraphIsEmpty) {
+  CorrelationClusterer clusterer;
+  UndirectedGraph graph;
+  std::vector<std::vector<int>> clusters = clusterer.Cluster(graph);
+  EXPECT_TRUE(clusters.empty());
+}
+
+TEST(CorrelationClusterer, OneClusterWhenAllNodesIsConnectedToANode) {
+  CorrelationClusterer clusterer;
+  UndirectedGraph graph;
+  graph.AddUndirectedEdgeAndNodeWeight(0, 1);
+  graph.AddUndirectedEdgeAndNodeWeight(0, 2);
+  graph.AddUndirectedEdgeAndNodeWeight(0, 3);
+  std::vector<std::vector<int>> clusters = clusterer.Cluster(graph);
+  EXPECT_THAT(clusters,
+              UnorderedElementsAreArray<std::vector<int>>({{0, 1, 2, 3}}));
+}
+
+}  // namespace ash
\ No newline at end of file
diff --git a/ash/public/cpp/tab_cluster/undirected_graph.cc b/ash/public/cpp/tab_cluster/undirected_graph.cc
new file mode 100644
index 0000000..e0ada60
--- /dev/null
+++ b/ash/public/cpp/tab_cluster/undirected_graph.cc
@@ -0,0 +1,57 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/public/cpp/tab_cluster/undirected_graph.h"
+
+#include "base/check.h"
+
+namespace ash {
+
+UndirectedGraph::UndirectedGraph() = default;
+UndirectedGraph::~UndirectedGraph() = default;
+
+void UndirectedGraph::AddEdge(size_t from_node, size_t to_node) {
+  EnsureSize(from_node);
+  EnsureSize(to_node);
+  ++adjacency_lists_[from_node][to_node];
+}
+
+void UndirectedGraph::AddNodeWeight(size_t id) {
+  EnsureSize(id);
+  if (id >= node_weights_.size()) {
+    node_weights_.resize(id + 1, 0.0);
+  }
+  ++node_weights_[id];
+  ++total_node_weight_;
+}
+
+void UndirectedGraph::AddUndirectedEdgeAndNodeWeight(size_t from_node,
+                                                     size_t to_node) {
+  AddEdge(from_node, to_node);
+  AddEdge(to_node, from_node);
+  AddNodeWeight(from_node);
+  AddNodeWeight(to_node);
+}
+
+void UndirectedGraph::EnsureSize(size_t id) {
+  if (static_cast<size_t>(adjacency_lists_.size()) < id + 1) {
+    adjacency_lists_.resize(id + 1);
+  }
+}
+
+size_t UndirectedGraph::NumNodes() const {
+  return adjacency_lists_.size();
+}
+
+size_t UndirectedGraph::NodeWeight(size_t id) const {
+  DCHECK(id >= 0 && id < NumNodes());
+  return node_weights_[id];
+}
+
+const std::map<size_t, double>& UndirectedGraph::Neighbors(size_t id) const {
+  DCHECK(id >= 0 && id < NumNodes());
+  return adjacency_lists_[id];
+}
+
+}  // namespace ash
\ No newline at end of file
diff --git a/ash/public/cpp/tab_cluster/undirected_graph.h b/ash/public/cpp/tab_cluster/undirected_graph.h
new file mode 100644
index 0000000..9a0209c
--- /dev/null
+++ b/ash/public/cpp/tab_cluster/undirected_graph.h
@@ -0,0 +1,45 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_PUBLIC_CPP_TAB_CLUSTER_UNDIRECTED_GRAPH_H_
+#define ASH_PUBLIC_CPP_TAB_CLUSTER_UNDIRECTED_GRAPH_H_
+
+#include <map>
+#include <vector>
+
+#include "ash/public/cpp/ash_public_export.h"
+
+namespace ash {
+
+// Data structure used for clustering.
+// TODO(crbug/1231303): //ash/public mostly contains interfaces, shared
+// constants, types and utility classes, which are used to share code between
+// //ash and //chrome. It's not common to put all implementation inside
+// //ash/public. Find a better place to move this class to.
+class ASH_PUBLIC_EXPORT UndirectedGraph {
+ public:
+  UndirectedGraph();
+  ~UndirectedGraph();
+
+  // Add node weight to both from_node and to_node.
+  void AddUndirectedEdgeAndNodeWeight(size_t from_node, size_t to_node);
+  size_t NumNodes() const;
+  size_t NodeWeight(size_t id) const;
+  const std::map<size_t, double>& Neighbors(size_t id) const;
+  size_t total_node_weight() const { return total_node_weight_; }
+
+ private:
+  void AddEdge(size_t from_node, size_t to_node);
+  void AddNodeWeight(size_t id);
+  // Expand adjacency_lists_.
+  void EnsureSize(size_t id);
+
+  std::vector<std::map<size_t, double>> adjacency_lists_;
+  std::vector<double> node_weights_;
+  size_t total_node_weight_ = 0;
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_TAB_CLUSTER_UNDIRECTED_GRAPH_H_
diff --git a/ash/public/cpp/tab_cluster/undirected_graph_unittest.cc b/ash/public/cpp/tab_cluster/undirected_graph_unittest.cc
new file mode 100644
index 0000000..68d11a1
--- /dev/null
+++ b/ash/public/cpp/tab_cluster/undirected_graph_unittest.cc
@@ -0,0 +1,34 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/public/cpp/tab_cluster/undirected_graph.h"
+
+#include "ash/public/cpp/tab_cluster/undirected_graph.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+TEST(UndirectedGraphTest, AddUndirectedEdgeAndNodeWeight) {
+  UndirectedGraph graph;
+  graph.AddUndirectedEdgeAndNodeWeight(0, 1);
+  graph.AddUndirectedEdgeAndNodeWeight(1, 0);
+  graph.AddUndirectedEdgeAndNodeWeight(0, 2);
+  graph.AddUndirectedEdgeAndNodeWeight(2, 3);
+  graph.AddUndirectedEdgeAndNodeWeight(3, 2);
+
+  EXPECT_EQ(graph.NumNodes(), (size_t)4);
+  EXPECT_EQ(graph.NodeWeight(0), (size_t)3);
+  EXPECT_EQ(graph.NodeWeight(1), (size_t)2);
+  EXPECT_EQ(graph.NodeWeight(2), (size_t)3);
+  EXPECT_EQ(graph.NodeWeight(3), (size_t)2);
+  EXPECT_EQ(graph.total_node_weight(), (size_t)10);
+
+  ASSERT_TRUE(graph.Neighbors(0).find(1) != graph.Neighbors(0).end());
+  EXPECT_EQ(graph.Neighbors(0).at(1), 2);
+  ASSERT_TRUE(graph.Neighbors(0).find(2) != graph.Neighbors(0).end());
+  EXPECT_EQ(graph.Neighbors(0).at(2), 1);
+}
+
+}  // namespace ash
\ No newline at end of file
diff --git a/ash/public/cpp/test/assistant_test_api.h b/ash/public/cpp/test/assistant_test_api.h
index ceb4113..a0612b806 100644
--- a/ash/public/cpp/test/assistant_test_api.h
+++ b/ash/public/cpp/test/assistant_test_api.h
@@ -86,10 +86,12 @@
 
   // Returns the top-level Assistant specific view.
   // Can only be used after the Assistant UI has been shown at least once.
+  // Exists for both bubble launcher and fullscreen launcher.
   virtual views::View* page_view() = 0;
 
   // Returns the Assistant main view.
   // Can only be used after the Assistant UI has been shown at least once.
+  // Only exists for fullscreen launcher.
   virtual views::View* main_view() = 0;
 
   // Returns the Assistant UI element container view, which contains all the
@@ -99,34 +101,42 @@
 
   // Returns the text field used for inputting new queries.
   // Can only be used after the Assistant UI has been shown at least once.
+  // Exists for both bubble launcher and fullscreen launcher.
   virtual views::Textfield* input_text_field() = 0;
 
   // Returns the mic field used for dictating new queries.
   // Can only be used after the Assistant UI has been shown at least once.
+  // Exists for both bubble launcher and fullscreen launcher.
   virtual views::View* mic_view() = 0;
 
   // Returns the greeting label shown when the Assistant is displayed.
   // Can only be used after the Assistant UI has been shown at least once.
+  // Exists for both bubble launcher and fullscreen launcher.
   virtual views::View* greeting_label() = 0;
 
   // Returns the button to enable voice mode.
   // Can only be used after the Assistant UI has been shown at least once.
+  // Exists for both bubble launcher and fullscreen launcher.
   virtual views::View* voice_input_toggle() = 0;
 
   // Returns the button to enable text mode.
   // Can only be used after the Assistant UI has been shown at least once.
+  // Exists for both bubble launcher and fullscreen launcher.
   virtual views::View* keyboard_input_toggle() = 0;
 
   // Returns the Assistant onboarding view.
   // Can only be used after the Assistant UI has been shown at least once.
+  // Exists for both bubble launcher and fullscreen launcher.
   virtual views::View* onboarding_view() = 0;
 
   // Returns the button to launch Assistant setup.
   // Can only be used after the Assistant UI has been shown at least once.
+  // Exists for both bubble launcher and fullscreen launcher.
   virtual views::View* opt_in_view() = 0;
 
   // Returns the view containing the suggestion chips.
   // Can only be used after the Assistant UI has been shown at least once.
+  // Exists for both bubble launcher and fullscreen launcher.
   virtual views::View* suggestion_chip_container() = 0;
 
   // Returns the window containing the Assistant UI.
@@ -136,6 +146,7 @@
 
   // Returns the app list view hosting the Assistant UI.
   // Can only be used after the Assistant UI has been shown at least once.
+  // Only exists for fullscreen launcher.
   virtual AppListView* app_list_view() = 0;
 
   // Returns the root window containing the Assistant UI (and the Ash shell).
diff --git a/ash/quick_pair/common/device.cc b/ash/quick_pair/common/device.cc
index 8e84c95..0b5cfb4 100644
--- a/ash/quick_pair/common/device.cc
+++ b/ash/quick_pair/common/device.cc
@@ -7,6 +7,20 @@
 #include <ostream>
 
 #include "ash/quick_pair/common/protocol.h"
+#include "base/memory/scoped_refptr.h"
+
+namespace {
+
+std::ostream& OutputToStream(std::ostream& stream,
+                             const std::string& metadata_id,
+                             const std::string& address,
+                             const ash::quick_pair::Protocol& protocol) {
+  stream << "[Device: metadata_id=" << metadata_id << ", address=" << address
+         << ", protocol=" << protocol << "]";
+  return stream;
+}
+
+}  // namespace
 
 namespace ash {
 namespace quick_pair {
@@ -17,10 +31,13 @@
       protocol(protocol) {}
 
 std::ostream& operator<<(std::ostream& stream, const Device& device) {
-  stream << "[Device: metadata_id=" << device.metadata_id
-         << ", address=" << device.address << ", protocol=" << device.protocol
-         << "]";
-  return stream;
+  return OutputToStream(stream, device.metadata_id, device.address,
+                        device.protocol);
+}
+
+std::ostream& operator<<(std::ostream& stream, scoped_refptr<Device> device) {
+  return OutputToStream(stream, device->metadata_id, device->address,
+                        device->protocol);
 }
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/common/device.h b/ash/quick_pair/common/device.h
index 619d415..16e0bc6 100644
--- a/ash/quick_pair/common/device.h
+++ b/ash/quick_pair/common/device.h
@@ -7,6 +7,8 @@
 
 #include "ash/quick_pair/common/protocol.h"
 #include "base/component_export.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
 
 namespace ash {
 namespace quick_pair {
@@ -18,13 +20,12 @@
 // fetch objects which contain more information. E.g. A Fast Pair component
 // can use |metadata_id| to query the Service to receive a full metadata
 // object.
-struct COMPONENT_EXPORT(QUICK_PAIR_COMMON) Device {
+struct COMPONENT_EXPORT(QUICK_PAIR_COMMON) Device
+    : public base::RefCounted<Device> {
   Device(std::string metadata_id, std::string address, Protocol protocol);
   Device(const Device&) = delete;
-  Device(Device&&) = default;
   Device& operator=(const Device&) = delete;
   Device& operator=(Device&&) = delete;
-  ~Device() = default;
 
   // An identifier which components can use to fetch additional metadata for
   // this device. This ID will correspond to different things depending on
@@ -37,11 +38,18 @@
 
   // The Quick Pair protocol implementation that this device belongs to.
   const Protocol protocol;
+
+ private:
+  friend class base::RefCounted<Device>;
+  ~Device() = default;
 };
 
 COMPONENT_EXPORT(QUICK_PAIR_COMMON)
 std::ostream& operator<<(std::ostream& stream, const Device& device);
 
+COMPONENT_EXPORT(QUICK_PAIR_COMMON)
+std::ostream& operator<<(std::ostream& stream, scoped_refptr<Device> device);
+
 }  // namespace quick_pair
 }  // namespace ash
 
diff --git a/ash/quick_pair/keyed_service/quick_pair_mediator.cc b/ash/quick_pair/keyed_service/quick_pair_mediator.cc
index 1e17717..e4ba92a 100644
--- a/ash/quick_pair/keyed_service/quick_pair_mediator.cc
+++ b/ash/quick_pair/keyed_service/quick_pair_mediator.cc
@@ -57,12 +57,12 @@
   SetFastPairState(is_enabled);
 }
 
-void Mediator::OnDeviceFound(const Device& device) {
+void Mediator::OnDeviceFound(scoped_refptr<Device> device) {
   QP_LOG(INFO) << __func__ << ": " << device;
   ui_broker_->ShowDiscovery(device);
 }
 
-void Mediator::OnDeviceLost(const Device& device) {
+void Mediator::OnDeviceLost(scoped_refptr<Device> device) {
   QP_LOG(INFO) << __func__ << ": " << device;
 }
 
@@ -75,12 +75,13 @@
     scanner_broker_->StopScanning(Protocol::kFastPair);
 }
 
-void Mediator::OnDiscoveryAction(const Device& device, DiscoveryAction action) {
+void Mediator::OnDiscoveryAction(scoped_refptr<Device> device,
+                                 DiscoveryAction action) {
   QP_LOG(INFO) << __func__ << ": Device=" << device << ", Action=" << action;
 
   switch (action) {
     case DiscoveryAction::kPairToDevice:
-      ui_broker_->ShowPairing(device);
+      ui_broker_->ShowPairing(std::move(device));
       break;
     case DiscoveryAction::kDismissedByUser:
     case DiscoveryAction::kDismissed:
@@ -88,17 +89,17 @@
   }
 }
 
-void Mediator::OnPairingFailureAction(const Device& device,
+void Mediator::OnPairingFailureAction(scoped_refptr<Device> device,
                                       PairingFailedAction action) {
   QP_LOG(INFO) << __func__ << ": Device=" << device << ", Action=" << action;
 }
 
-void Mediator::OnCompanionAppAction(const Device& device,
+void Mediator::OnCompanionAppAction(scoped_refptr<Device> device,
                                     CompanionAppAction action) {
   QP_LOG(INFO) << __func__ << ": Device=" << device << ", Action=" << action;
 }
 
-void Mediator::OnAssociateAccountAction(const Device& device,
+void Mediator::OnAssociateAccountAction(scoped_refptr<Device> device,
                                         AssociateAccountAction action) {
   QP_LOG(INFO) << __func__ << ": Device=" << device << ", Action=" << action;
 }
diff --git a/ash/quick_pair/keyed_service/quick_pair_mediator.h b/ash/quick_pair/keyed_service/quick_pair_mediator.h
index 60256822..4e6d19e 100644
--- a/ash/quick_pair/keyed_service/quick_pair_mediator.h
+++ b/ash/quick_pair/keyed_service/quick_pair_mediator.h
@@ -7,15 +7,17 @@
 
 #include <memory>
 
-#include "ash/quick_pair/common/device.h"
 #include "ash/quick_pair/feature_status_tracker/quick_pair_feature_status_tracker.h"
 #include "ash/quick_pair/scanning/scanner_broker.h"
 #include "ash/quick_pair/ui/ui_broker.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/scoped_observation.h"
 
 namespace ash {
 namespace quick_pair {
 
+struct Device;
+
 // Implements the Mediator design pattern for the components in the Quick Pair
 // system, e.g. the UI Broker, Scanning Broker and Pairing Broker.
 class Mediator : public FeatureStatusTracker::Observer,
@@ -43,16 +45,17 @@
   void OnFastPairEnabledChanged(bool is_enabled) override;
 
   // SannerBroker::Observer
-  void OnDeviceFound(const Device& device) override;
-  void OnDeviceLost(const Device& device) override;
+  void OnDeviceFound(scoped_refptr<Device> device) override;
+  void OnDeviceLost(scoped_refptr<Device> device) override;
 
   // UIBroker::Observer
-  void OnDiscoveryAction(const Device& device, DiscoveryAction action) override;
-  void OnPairingFailureAction(const Device& device,
+  void OnDiscoveryAction(scoped_refptr<Device> device,
+                         DiscoveryAction action) override;
+  void OnPairingFailureAction(scoped_refptr<Device> device,
                               PairingFailedAction action) override;
-  void OnCompanionAppAction(const Device& device,
+  void OnCompanionAppAction(scoped_refptr<Device> device,
                             CompanionAppAction action) override;
-  void OnAssociateAccountAction(const Device& device,
+  void OnAssociateAccountAction(scoped_refptr<Device> device,
                                 AssociateAccountAction action) override;
 
  private:
diff --git a/ash/quick_pair/keyed_service/quick_pair_mediator_unittest.cc b/ash/quick_pair/keyed_service/quick_pair_mediator_unittest.cc
index 5dc52e5..2f760fb 100644
--- a/ash/quick_pair/keyed_service/quick_pair_mediator_unittest.cc
+++ b/ash/quick_pair/keyed_service/quick_pair_mediator_unittest.cc
@@ -15,8 +15,16 @@
 #include "ash/quick_pair/scanning/scanner_broker.h"
 #include "ash/quick_pair/ui/mock_ui_broker.h"
 #include "ash/quick_pair/ui/ui_broker.h"
+#include "base/memory/scoped_refptr.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace {
+
+constexpr char kTestMetadataId[] = "test_metadata_id";
+constexpr char kTestAddress[] = "test_address";
+
+}  // namespace
+
 namespace ash {
 namespace quick_pair {
 
@@ -38,10 +46,13 @@
 
     mediator_ = std::make_unique<Mediator>(
         std::move(tracker), std::move(scanner_broker), std::move(ui_broker));
+
+    device_ = base::MakeRefCounted<Device>(kTestMetadataId, kTestAddress,
+                                           Protocol::kFastPair);
   }
 
  protected:
-  Device device_{"test_metadata_id", "test_address", Protocol::kFastPair};
+  scoped_refptr<Device> device_;
   FakeFeatureStatusTracker* feature_status_tracker_;
   MockScannerBroker* mock_scanner_broker_;
   MockUIBroker* mock_ui_broker_;
diff --git a/ash/quick_pair/pairing/BUILD.gn b/ash/quick_pair/pairing/BUILD.gn
index fd2815d..2e443c7 100644
--- a/ash/quick_pair/pairing/BUILD.gn
+++ b/ash/quick_pair/pairing/BUILD.gn
@@ -15,6 +15,10 @@
     "fast_pair/decrypted_response.h",
     "fast_pair/fast_pair_data_parser.cc",
     "fast_pair/fast_pair_data_parser.h",
+    "fast_pair/fast_pair_encryption.cc",
+    "fast_pair/fast_pair_encryption.h",
+    "fast_pair/fast_pair_key_pair.cc",
+    "fast_pair/fast_pair_key_pair.h",
     "fast_pair/fast_pair_pairer.cc",
     "fast_pair/fast_pair_pairer.h",
     "pairer_broker.h",
@@ -52,6 +56,7 @@
 
   sources = [
     "fast_pair/fast_pair_data_parser_unittest.cc",
+    "fast_pair/fast_pair_encryption_unittest.cc",
     "fast_pair/fast_pair_pairer_unittest.cc",
   ]
 
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.cc
new file mode 100644
index 0000000..57c1e8f
--- /dev/null
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.cc
@@ -0,0 +1,110 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <array>
+
+#include "ash/quick_pair/pairing/fast_pair/fast_pair_encryption.h"
+
+#include "ash/quick_pair/pairing/fast_pair/fast_pair_key_pair.h"
+#include "third_party/boringssl/src/include/openssl/ecdh.h"
+#include "third_party/boringssl/src/include/openssl/nid.h"
+#include "third_party/boringssl/src/include/openssl/sha.h"
+
+namespace {
+
+// These are values to be set in tests using |SetKeysForEcdhKeyAgreement |to use
+// for as EC_GROUP and EC_KEY to create consistency in tests, since otherwise
+// the generation is different every time.
+bssl::UniquePtr<EC_GROUP> g_test_ec_group = nullptr;
+bssl::UniquePtr<EC_KEY> g_test_ec_key = nullptr;
+
+// Converts the public anti-spoofing key into an EC_Point.
+bssl::UniquePtr<EC_POINT> GetEcPointFromPublicAntiSpoofingKey(
+    const bssl::UniquePtr<EC_GROUP>& ec_group,
+    const std::string& decoded_public_anti_spoofing) {
+  std::array<uint8_t, kPublicKeyByteSize + 1> buffer;
+  buffer[0] = POINT_CONVERSION_UNCOMPRESSED;
+  std::copy(decoded_public_anti_spoofing.begin(),
+            decoded_public_anti_spoofing.end(), buffer.begin() + 1);
+
+  bssl::UniquePtr<EC_POINT> new_ec_point(EC_POINT_new(ec_group.get()));
+  EC_POINT_oct2point(ec_group.get(), new_ec_point.get(), buffer.data(),
+                     buffer.size(), nullptr);
+
+  return new_ec_point;
+}
+
+// Key derivation function to be used in hashing the generated secret key.
+void* KDF(const void* in, size_t inlen, void* out, size_t* outlen) {
+  // Set this to 16 since that's the amount of bytes we want to use
+  // for the key, even though more will be written by SHA256 below.
+  *outlen = kPrivateKeyByteSize;
+  return SHA256(static_cast<const uint8_t*>(in), inlen,
+                static_cast<uint8_t*>(out));
+}
+
+}  // namespace
+
+namespace ash {
+namespace quick_pair {
+namespace fast_pair_encryption {
+
+KeyPair GenerateKeysWithEcdhKeyAgreement(
+    const std::string& decoded_public_anti_spoofing) {
+  // Generate the secp256r1 key-pair.
+  bssl::UniquePtr<EC_GROUP> new_ec_group(
+      EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+  bssl::UniquePtr<EC_GROUP> ec_group =
+      g_test_ec_group ? std::move(g_test_ec_group) : std::move(new_ec_group);
+  bssl::UniquePtr<EC_KEY> new_ec_key(
+      EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+  EC_KEY_generate_key(new_ec_key.get());
+  bssl::UniquePtr<EC_KEY> ec_key =
+      g_test_ec_key ? std::move(g_test_ec_key) : std::move(new_ec_key);
+
+  // The ultimate goal here is to get a 64-byte public key. We accomplish this
+  // by converting the generated public key into the uncompressed X9.62 format,
+  // which is 0x04 followed by padded x and y coordinates.
+  std::array<uint8_t, kPublicKeyByteSize + 1> uncompressed_private_key;
+  EC_POINT_point2oct(ec_group.get(), EC_KEY_get0_public_key(ec_key.get()),
+                     POINT_CONVERSION_UNCOMPRESSED,
+                     uncompressed_private_key.data(),
+                     uncompressed_private_key.size(), nullptr);
+
+  // Generate the secret for use during encryption. Cannot use std::array
+  // because size is determined by variable |secret_len|.
+  size_t secret_len = (EC_GROUP_get_degree(ec_group.get()) + 7) / 8;
+  uint8_t secret[secret_len];
+
+  ECDH_compute_key(secret, secret_len,
+                   GetEcPointFromPublicAntiSpoofingKey(
+                       ec_group, decoded_public_anti_spoofing)
+                       .get(),
+                   ec_key.get(), &KDF);
+
+  // Ensure that the secret is 16 bytes. Cannot use std::copy since |secret| is
+  // a variably modified type, thus template is not fixed at compile time.
+  std::array<uint8_t, kPrivateKeyByteSize> private_key;
+  for (int i = 0; i < kPrivateKeyByteSize; ++i)
+    private_key[i] = secret[i];
+
+  // Ignore the first byte since it is 0x04, from the above uncompressed X9.62
+  // format.
+  std::array<uint8_t, kPublicKeyByteSize> public_key;
+  std::copy(uncompressed_private_key.begin() + 1,
+            uncompressed_private_key.end(), public_key.begin());
+
+  return KeyPair(private_key, public_key);
+}
+
+void SetKeysForEcdhKeyAgreement(bssl::UniquePtr<EC_GROUP> ec_group_for_testing,
+                                bssl::UniquePtr<EC_KEY> ec_key_for_testing) {
+  g_test_ec_group = std::move(ec_group_for_testing);
+  g_test_ec_key = std::move(ec_key_for_testing);
+}
+
+}  // namespace fast_pair_encryption
+}  // namespace quick_pair
+}  // namespace ash
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.h b/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.h
new file mode 100644
index 0000000..f5d41ba
--- /dev/null
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.h
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_QUICK_PAIR_PAIRING_FAST_PAIR_FAST_PAIR_ENCRYPTION_H_
+#define ASH_QUICK_PAIR_PAIRING_FAST_PAIR_FAST_PAIR_ENCRYPTION_H_
+
+#include <string>
+
+#include "ash/quick_pair/pairing/fast_pair/fast_pair_key_pair.h"
+#include "base/component_export.h"
+#include "third_party/boringssl/src/include/openssl/base.h"
+#include "third_party/boringssl/src/include/openssl/ec.h"
+#include "third_party/boringssl/src/include/openssl/ec_key.h"
+
+namespace ash {
+namespace quick_pair {
+namespace fast_pair_encryption {
+
+COMPONENT_EXPORT(QUICK_PAIR_PAIRING)
+KeyPair GenerateKeysWithEcdhKeyAgreement(
+    const std::string& decoded_public_anti_spoofing);
+
+COMPONENT_EXPORT(QUICK_PAIR_PAIRING)
+void SetKeysForEcdhKeyAgreement(bssl::UniquePtr<EC_GROUP> ec_group,
+                                bssl::UniquePtr<EC_KEY> ec_key);
+
+}  // namespace fast_pair_encryption
+}  // namespace quick_pair
+}  // namespace ash
+
+#endif  // ASH_QUICK_PAIR_PAIRING_FAST_PAIR_FAST_PAIR_ENCRYPTION_H_
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_encryption_unittest.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_encryption_unittest.cc
new file mode 100644
index 0000000..e2639d3
--- /dev/null
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_encryption_unittest.cc
@@ -0,0 +1,101 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/quick_pair/pairing/fast_pair/fast_pair_encryption.h"
+
+#include <stddef.h>
+
+#include "ash/quick_pair/common/logging.h"
+#include "ash/quick_pair/pairing/fast_pair/fast_pair_key_pair.h"
+#include "base/base64.h"
+#include "base/strings/string_number_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/boringssl/src/include/openssl/bn.h"
+#include "third_party/boringssl/src/include/openssl/bytestring.h"
+#include "third_party/boringssl/src/include/openssl/ec.h"
+#include "third_party/boringssl/src/include/openssl/ec_key.h"
+#include "third_party/boringssl/src/include/openssl/ecdh.h"
+#include "third_party/boringssl/src/include/openssl/nid.h"
+
+namespace ash {
+namespace quick_pair {
+namespace fast_pair_encryption {
+
+class FastPairEncryptionTest : public testing::Test {};
+
+TEST_F(FastPairEncryptionTest, SuccessfulEcdhKeyAgreement) {
+  // Make a P-256 key and extract the affine coordinates.
+  bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+  ASSERT_TRUE(key);
+  ASSERT_TRUE(EC_KEY_generate_key(key.get()));
+
+  // Make an arbitrary curve which is identical to P-256.
+  static const uint8_t kP[] = {
+      0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
+      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  };
+  static const uint8_t kA[] = {
+      0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
+      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc,
+  };
+  static const uint8_t kB[] = {
+      0x5a, 0xc6, 0x35, 0xd8, 0xaa, 0x3a, 0x93, 0xe7, 0xb3, 0xeb, 0xbd,
+      0x55, 0x76, 0x98, 0x86, 0xbc, 0x65, 0x1d, 0x06, 0xb0, 0xcc, 0x53,
+      0xb0, 0xf6, 0x3b, 0xce, 0x3c, 0x3e, 0x27, 0xd2, 0x60, 0x4b,
+  };
+  static const uint8_t kX[] = {
+      0x6b, 0x17, 0xd1, 0xf2, 0xe1, 0x2c, 0x42, 0x47, 0xf8, 0xbc, 0xe6,
+      0xe5, 0x63, 0xa4, 0x40, 0xf2, 0x77, 0x03, 0x7d, 0x81, 0x2d, 0xeb,
+      0x33, 0xa0, 0xf4, 0xa1, 0x39, 0x45, 0xd8, 0x98, 0xc2, 0x96,
+  };
+  static const uint8_t kY[] = {
+      0x4f, 0xe3, 0x42, 0xe2, 0xfe, 0x1a, 0x7f, 0x9b, 0x8e, 0xe7, 0xeb,
+      0x4a, 0x7c, 0x0f, 0x9e, 0x16, 0x2b, 0xce, 0x33, 0x57, 0x6b, 0x31,
+      0x5e, 0xce, 0xcb, 0xb6, 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5,
+  };
+  static const uint8_t kOrder[] = {
+      0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
+      0xff, 0xff, 0xff, 0xff, 0xff, 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17,
+      0x9e, 0x84, 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51,
+  };
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
+  ASSERT_TRUE(ctx);
+  bssl::UniquePtr<BIGNUM> p(BN_bin2bn(kP, sizeof(kP), nullptr));
+  ASSERT_TRUE(p);
+  bssl::UniquePtr<BIGNUM> a(BN_bin2bn(kA, sizeof(kA), nullptr));
+  ASSERT_TRUE(a);
+  bssl::UniquePtr<BIGNUM> b(BN_bin2bn(kB, sizeof(kB), nullptr));
+  ASSERT_TRUE(b);
+  bssl::UniquePtr<BIGNUM> gx(BN_bin2bn(kX, sizeof(kX), nullptr));
+  ASSERT_TRUE(gx);
+  bssl::UniquePtr<BIGNUM> gy(BN_bin2bn(kY, sizeof(kY), nullptr));
+  ASSERT_TRUE(gy);
+  bssl::UniquePtr<BIGNUM> order(BN_bin2bn(kOrder, sizeof(kOrder), nullptr));
+  ASSERT_TRUE(order);
+
+  bssl::UniquePtr<EC_GROUP> group(
+      EC_GROUP_new_curve_GFp(p.get(), a.get(), b.get(), ctx.get()));
+  ASSERT_TRUE(group);
+  SetKeysForEcdhKeyAgreement(std::move(group), std::move(key));
+
+  std::string public_anti_spoof =
+      "Wuyr48lD3txnUhGiMF1IfzlTwRxxe+wMB1HLzP+"
+      "0wVcljfT3XPoiy1fntlneziyLD5knDVAJSE+RM/zlPRP/Jg==";
+  std::string decoded_key;
+  base::Base64Decode(public_anti_spoof, &decoded_key);
+  KeyPair keys = GenerateKeysWithEcdhKeyAgreement(decoded_key);
+
+  EXPECT_EQ("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+            base::HexEncode(keys.private_key));
+  EXPECT_EQ(
+      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+      base::HexEncode(keys.public_key));
+}
+
+}  // namespace fast_pair_encryption
+}  // namespace quick_pair
+}  // namespace ash
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_key_pair.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_key_pair.cc
new file mode 100644
index 0000000..dde6eb29
--- /dev/null
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_key_pair.cc
@@ -0,0 +1,25 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/quick_pair/common/device.h"
+
+#include <ostream>
+
+#include "ash/quick_pair/common/protocol.h"
+#include "ash/quick_pair/pairing/fast_pair/fast_pair_key_pair.h"
+#include "base/strings/string_number_conversions.h"
+
+namespace ash {
+namespace quick_pair {
+namespace fast_pair_encryption {
+
+KeyPair::KeyPair(std::array<uint8_t, kPrivateKeyByteSize> private_key,
+                 std::array<uint8_t, kPublicKeyByteSize> public_key)
+    : private_key(std::move(private_key)), public_key(std::move(public_key)) {}
+
+KeyPair::KeyPair(KeyPair&&) = default;
+
+}  // namespace fast_pair_encryption
+}  // namespace quick_pair
+}  // namespace ash
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_key_pair.h b/ash/quick_pair/pairing/fast_pair/fast_pair_key_pair.h
new file mode 100644
index 0000000..a30c02e
--- /dev/null
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_key_pair.h
@@ -0,0 +1,45 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_QUICK_PAIR_PAIRING_FAST_PAIR_FAST_PAIR_KEY_PAIR_H_
+#define ASH_QUICK_PAIR_PAIRING_FAST_PAIR_FAST_PAIR_KEY_PAIR_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <array>
+
+#include "base/component_export.h"
+
+namespace {
+
+constexpr int kPrivateKeyByteSize = 16;
+constexpr int kPublicKeyByteSize = 64;
+
+}  // namespace
+
+namespace ash {
+namespace quick_pair {
+namespace fast_pair_encryption {
+
+// Key pair structure to represent public and private keys used for encryption/
+// decryption.
+struct COMPONENT_EXPORT(QUICK_PAIR_PAIRING) KeyPair {
+  KeyPair(std::array<uint8_t, kPrivateKeyByteSize> private_key,
+          std::array<uint8_t, kPublicKeyByteSize> public_key);
+  KeyPair(const KeyPair&) = delete;
+  KeyPair(KeyPair&&);
+  KeyPair& operator=(const KeyPair&) = delete;
+  KeyPair& operator=(KeyPair&&) = delete;
+  ~KeyPair() = default;
+
+  const std::array<uint8_t, kPrivateKeyByteSize> private_key;
+  const std::array<uint8_t, kPublicKeyByteSize> public_key;
+};
+
+}  // namespace fast_pair_encryption
+}  // namespace quick_pair
+}  // namespace ash
+
+#endif  // ASH_QUICK_PAIR_PAIRING_FAST_PAIR_FAST_PAIR_KEY_PAIR_H_
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer.cc
index dbf3bfc2..83847dc 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer.cc
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer.cc
@@ -14,13 +14,14 @@
 namespace quick_pair {
 
 FastPairPairer::FastPairPairer(
-    const Device& device,
-    base::OnceCallback<void(const Device&)> paired_callback,
-    base::OnceCallback<void(const Device&, PairFailure)> pair_failed_callback,
-    base::OnceCallback<void(const Device&, AccountKeyFailure)>
+    scoped_refptr<Device> device,
+    base::OnceCallback<void(scoped_refptr<Device>)> paired_callback,
+    base::OnceCallback<void(scoped_refptr<Device>, PairFailure)>
+        pair_failed_callback,
+    base::OnceCallback<void(scoped_refptr<Device>, AccountKeyFailure)>
         account_key_failure_callback,
-    base::OnceCallback<void(const Device&)> pairing_procedure_complete)
-    : device_(device),
+    base::OnceCallback<void(scoped_refptr<Device>)> pairing_procedure_complete)
+    : device_(std::move(device)),
       paired_callback_(std::move(paired_callback)),
       pair_failed_callback_(std::move(pair_failed_callback)),
       account_key_failure_callback_(std::move(account_key_failure_callback)),
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer.h b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer.h
index 7ef3729..7b97df2 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer.h
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer.h
@@ -5,13 +5,13 @@
 #ifndef ASH_QUICK_PAIR_PAIRING_FAST_PAIR_FAST_PAIR_PAIRER_H_
 #define ASH_QUICK_PAIR_PAIRING_FAST_PAIR_FAST_PAIR_PAIRER_H_
 
-#include <functional>
-#include "ash/quick_pair/common/device.h"
 #include "base/callback.h"
+#include "base/memory/scoped_refptr.h"
 
 namespace ash {
 namespace quick_pair {
 
+struct Device;
 enum class AccountKeyFailure;
 enum class PairFailure;
 
@@ -20,12 +20,14 @@
 class FastPairPairer {
  public:
   FastPairPairer(
-      const Device& device,
-      base::OnceCallback<void(const Device&)> paired_callback,
-      base::OnceCallback<void(const Device&, PairFailure)> pair_failed_callback,
-      base::OnceCallback<void(const Device&, AccountKeyFailure)>
+      scoped_refptr<Device> device,
+      base::OnceCallback<void(scoped_refptr<Device>)> paired_callback,
+      base::OnceCallback<void(scoped_refptr<Device>, PairFailure)>
+          pair_failed_callback,
+      base::OnceCallback<void(scoped_refptr<Device>, AccountKeyFailure)>
           account_key_failure_callback,
-      base::OnceCallback<void(const Device&)> pairing_procedure_complete);
+      base::OnceCallback<void(scoped_refptr<Device>)>
+          pairing_procedure_complete);
   FastPairPairer(const FastPairPairer&) = delete;
   FastPairPairer& operator=(const FastPairPairer&) = delete;
   FastPairPairer(FastPairPairer&&);
@@ -35,12 +37,13 @@
  private:
   void StartPairing();
 
-  std::reference_wrapper<const Device> device_;
-  base::OnceCallback<void(const Device&)> paired_callback_;
-  base::OnceCallback<void(const Device&, PairFailure)> pair_failed_callback_;
-  base::OnceCallback<void(const Device&, AccountKeyFailure)>
+  scoped_refptr<Device> device_;
+  base::OnceCallback<void(scoped_refptr<Device>)> paired_callback_;
+  base::OnceCallback<void(scoped_refptr<Device>, PairFailure)>
+      pair_failed_callback_;
+  base::OnceCallback<void(scoped_refptr<Device>, AccountKeyFailure)>
       account_key_failure_callback_;
-  base::OnceCallback<void(const Device&)> pairing_procedure_complete_;
+  base::OnceCallback<void(scoped_refptr<Device>)> pairing_procedure_complete_;
 };
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_unittest.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_unittest.cc
index 6a2a07e..95f7783 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_unittest.cc
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_unittest.cc
@@ -10,13 +10,27 @@
 #include "ash/quick_pair/common/device.h"
 #include "ash/quick_pair/common/pair_failure.h"
 #include "ash/quick_pair/common/protocol.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/test/mock_callback.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace {
+
+constexpr char kMetadataId[] = "test_metadata_id";
+constexpr char kAddress[] = "test_address";
+
+}  // namespace
+
 namespace ash {
 namespace quick_pair {
 
 class FastPairPairerTest : public testing::Test {
+ public:
+  void SetUp() override {
+    device_ = base::MakeRefCounted<Device>(kMetadataId, kAddress,
+                                           Protocol::kFastPair);
+  }
+
  protected:
   // This is done on-demand to enable setting up mock expectations first.
   void CreatePairer() {
@@ -25,13 +39,16 @@
         account_key_failure_callback_.Get(), pairing_procedure_complete_.Get());
   }
 
-  Device device_{"test_id", "test_address", Protocol::kFastPair};
-  base::MockCallback<base::OnceCallback<void(const Device&)>> paired_callback_;
-  base::MockCallback<base::OnceCallback<void(const Device&, PairFailure)>>
+  scoped_refptr<Device> device_;
+  base::MockCallback<base::OnceCallback<void(scoped_refptr<Device>)>>
+      paired_callback_;
+  base::MockCallback<
+      base::OnceCallback<void(scoped_refptr<Device>, PairFailure)>>
       pair_failed_callback_;
-  base::MockCallback<base::OnceCallback<void(const Device&, AccountKeyFailure)>>
+  base::MockCallback<
+      base::OnceCallback<void(scoped_refptr<Device>, AccountKeyFailure)>>
       account_key_failure_callback_;
-  base::MockCallback<base::OnceCallback<void(const Device&)>>
+  base::MockCallback<base::OnceCallback<void(scoped_refptr<Device>)>>
       pairing_procedure_complete_;
   std::unique_ptr<FastPairPairer> pairer_;
 };
diff --git a/ash/quick_pair/pairing/mock_pairer_broker.cc b/ash/quick_pair/pairing/mock_pairer_broker.cc
index a819dc7..491198a 100644
--- a/ash/quick_pair/pairing/mock_pairer_broker.cc
+++ b/ash/quick_pair/pairing/mock_pairer_broker.cc
@@ -22,19 +22,19 @@
   observers_.RemoveObserver(observer);
 }
 
-void MockPairerBroker::NotifyDevicePaired(const Device& device) {
+void MockPairerBroker::NotifyDevicePaired(scoped_refptr<Device> device) {
   for (auto& obs : observers_)
     obs.OnDevicePaired(device);
 }
 
-void MockPairerBroker::NotifyPairFailure(const Device& device,
+void MockPairerBroker::NotifyPairFailure(scoped_refptr<Device> device,
                                          PairFailure failure) {
   for (auto& obs : observers_)
     obs.OnPairFailure(device, failure);
 }
 
 void MockPairerBroker::NotifyAccountKeyWrite(
-    const Device& device,
+    scoped_refptr<Device> device,
     absl::optional<AccountKeyFailure> failure) {
   for (auto& obs : observers_)
     obs.OnAccountKeyWrite(device, failure);
diff --git a/ash/quick_pair/pairing/mock_pairer_broker.h b/ash/quick_pair/pairing/mock_pairer_broker.h
index de30275..46996dc 100644
--- a/ash/quick_pair/pairing/mock_pairer_broker.h
+++ b/ash/quick_pair/pairing/mock_pairer_broker.h
@@ -25,13 +25,13 @@
   MockPairerBroker& operator=(const MockPairerBroker&) = delete;
   ~MockPairerBroker() override;
 
-  MOCK_METHOD(void, PairDevice, (const Device&), (override));
+  MOCK_METHOD(void, PairDevice, (scoped_refptr<Device>), (override));
 
   void AddObserver(Observer* observer) override;
   void RemoveObserver(Observer* observer) override;
-  void NotifyDevicePaired(const Device& device);
-  void NotifyPairFailure(const Device& device, PairFailure failure);
-  void NotifyAccountKeyWrite(const Device& device,
+  void NotifyDevicePaired(scoped_refptr<Device> device);
+  void NotifyPairFailure(scoped_refptr<Device> device, PairFailure failure);
+  void NotifyAccountKeyWrite(scoped_refptr<Device> device,
                              absl::optional<AccountKeyFailure> error);
 
  private:
diff --git a/ash/quick_pair/pairing/pairer_broker.h b/ash/quick_pair/pairing/pairer_broker.h
index 2436e87..76f62bf0 100644
--- a/ash/quick_pair/pairing/pairer_broker.h
+++ b/ash/quick_pair/pairing/pairer_broker.h
@@ -24,9 +24,10 @@
  public:
   class Observer : public base::CheckedObserver {
    public:
-    virtual void OnDevicePaired(const Device& device) = 0;
-    virtual void OnPairFailure(const Device& device, PairFailure failure) = 0;
-    virtual void OnAccountKeyWrite(const Device& device,
+    virtual void OnDevicePaired(scoped_refptr<Device> device) = 0;
+    virtual void OnPairFailure(scoped_refptr<Device> device,
+                               PairFailure failure) = 0;
+    virtual void OnAccountKeyWrite(scoped_refptr<Device> device,
                                    absl::optional<AccountKeyFailure> error) = 0;
   };
 
@@ -34,7 +35,7 @@
 
   virtual void AddObserver(Observer* observer) = 0;
   virtual void RemoveObserver(Observer* observer) = 0;
-  virtual void PairDevice(const Device& device) = 0;
+  virtual void PairDevice(scoped_refptr<Device> device) = 0;
 };
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/pairing/pairer_broker_impl.cc b/ash/quick_pair/pairing/pairer_broker_impl.cc
index a232587..026085f 100644
--- a/ash/quick_pair/pairing/pairer_broker_impl.cc
+++ b/ash/quick_pair/pairing/pairer_broker_impl.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "ash/quick_pair/pairing/pairer_broker_impl.h"
+#include <memory>
 
 #include "ash/quick_pair/common/account_key_failure.h"
 #include "ash/quick_pair/common/device.h"
@@ -13,6 +14,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/containers/contains.h"
+#include "base/memory/scoped_refptr.h"
 
 namespace ash {
 namespace quick_pair {
@@ -29,23 +31,23 @@
   observers_.RemoveObserver(observer);
 }
 
-void PairerBrokerImpl::PairDevice(const Device& device) {
-  switch (device.protocol) {
+void PairerBrokerImpl::PairDevice(scoped_refptr<Device> device) {
+  switch (device->protocol) {
     case Protocol::kFastPair:
-      PairFastPairDevice(device);
+      PairFastPairDevice(std::move(device));
       break;
   }
 }
 
-void PairerBrokerImpl::PairFastPairDevice(const Device& device) {
-  if (base::Contains(fast_pair_pairers_, device.address)) {
+void PairerBrokerImpl::PairFastPairDevice(scoped_refptr<Device> device) {
+  if (base::Contains(fast_pair_pairers_, device->address)) {
     QP_LOG(WARNING) << __func__ << ": Already pairing device" << device;
     return;
   }
 
   QP_LOG(INFO) << __func__ << ": " << device;
 
-  fast_pair_pairers_[device.address] = std::make_unique<FastPairPairer>(
+  fast_pair_pairers_[device->address] = std::make_unique<FastPairPairer>(
       device,
       base::BindOnce(&PairerBrokerImpl::OnFastPairDevicePaired,
                      weak_pointer_factory_.GetWeakPtr()),
@@ -57,23 +59,24 @@
                      weak_pointer_factory_.GetWeakPtr()));
 }
 
-void PairerBrokerImpl::OnFastPairDevicePaired(const Device& device) {
+void PairerBrokerImpl::OnFastPairDevicePaired(scoped_refptr<Device> device) {
   QP_LOG(INFO) << __func__ << ": Device=" << device;
 }
 
-void PairerBrokerImpl::OnFastPairPairingFailure(const Device& device,
+void PairerBrokerImpl::OnFastPairPairingFailure(scoped_refptr<Device> device,
                                                 PairFailure failure) {
   QP_LOG(INFO) << __func__ << ": Device=" << device << ", Failure=" << failure;
 }
 
-void PairerBrokerImpl::OnAccountKeyFailure(const Device& device,
+void PairerBrokerImpl::OnAccountKeyFailure(scoped_refptr<Device> device,
                                            AccountKeyFailure failure) {
   QP_LOG(INFO) << __func__ << ": Device=" << device << ", Failure=" << failure;
 }
 
-void PairerBrokerImpl::OnFastPairProcedureComplete(const Device& device) {
+void PairerBrokerImpl::OnFastPairProcedureComplete(
+    scoped_refptr<Device> device) {
   QP_LOG(INFO) << __func__ << ": Device=" << device;
-  fast_pair_pairers_.erase(device.address);
+  fast_pair_pairers_.erase(device->address);
 }
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/pairing/pairer_broker_impl.h b/ash/quick_pair/pairing/pairer_broker_impl.h
index 8edafff..247d54d 100644
--- a/ash/quick_pair/pairing/pairer_broker_impl.h
+++ b/ash/quick_pair/pairing/pairer_broker_impl.h
@@ -14,6 +14,7 @@
 #include "ash/quick_pair/common/pair_failure.h"
 #include "ash/quick_pair/common/protocol.h"
 #include "base/containers/flat_map.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -34,14 +35,16 @@
   // PairingBroker:
   void AddObserver(Observer* observer) override;
   void RemoveObserver(Observer* observer) override;
-  void PairDevice(const Device& device) override;
+  void PairDevice(scoped_refptr<Device> device) override;
 
  private:
-  void PairFastPairDevice(const Device& device);
-  void OnFastPairDevicePaired(const Device& device);
-  void OnFastPairPairingFailure(const Device& device, PairFailure failure);
-  void OnAccountKeyFailure(const Device& device, AccountKeyFailure failure);
-  void OnFastPairProcedureComplete(const Device& device);
+  void PairFastPairDevice(scoped_refptr<Device> device);
+  void OnFastPairDevicePaired(scoped_refptr<Device> device);
+  void OnFastPairPairingFailure(scoped_refptr<Device> device,
+                                PairFailure failure);
+  void OnAccountKeyFailure(scoped_refptr<Device> device,
+                           AccountKeyFailure failure);
+  void OnFastPairProcedureComplete(scoped_refptr<Device> device);
 
   base::flat_map<std::string, std::unique_ptr<FastPairPairer>>
       fast_pair_pairers_;
diff --git a/ash/quick_pair/scanning/fast_pair/fast_pair_scanner.h b/ash/quick_pair/scanning/fast_pair/fast_pair_scanner.h
index 9c6d45f..eaa3610 100644
--- a/ash/quick_pair/scanning/fast_pair/fast_pair_scanner.h
+++ b/ash/quick_pair/scanning/fast_pair/fast_pair_scanner.h
@@ -5,6 +5,7 @@
 #ifndef ASH_QUICK_PAIR_SCANNING_FAST_PAIR_FAST_PAIR_SCANNER_H_
 #define ASH_QUICK_PAIR_SCANNING_FAST_PAIR_FAST_PAIR_SCANNER_H_
 
+#include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
 #include "device/bluetooth/bluetooth_device.h"
@@ -14,7 +15,7 @@
 
 // This registers a BluetoothLowEnergyScanner with the Advertisement Monitoring
 // API and exposes the Fast Pair devices found/lost events to its observers.
-class FastPairScanner {
+class FastPairScanner : public base::RefCounted<FastPairScanner> {
  public:
   class Observer : public base::CheckedObserver {
    public:
@@ -22,10 +23,14 @@
     virtual void OnDeviceLost(device::BluetoothDevice* device) = 0;
   };
 
-  virtual ~FastPairScanner() = default;
-
   virtual void AddObserver(Observer* observer) = 0;
   virtual void RemoveObserver(Observer* observer) = 0;
+
+ protected:
+  virtual ~FastPairScanner() = default;
+
+ private:
+  friend base::RefCounted<FastPairScanner>;
 };
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/scanning/fast_pair/fast_pair_scanner_impl.cc b/ash/quick_pair/scanning/fast_pair/fast_pair_scanner_impl.cc
index 40b073b..e258d05 100644
--- a/ash/quick_pair/scanning/fast_pair/fast_pair_scanner_impl.cc
+++ b/ash/quick_pair/scanning/fast_pair/fast_pair_scanner_impl.cc
@@ -13,10 +13,8 @@
 
 namespace {
 
-constexpr int16_t kFilterDeviceFoundThreshold = -80;
 constexpr base::TimeDelta kFilterDeviceFoundTimeout =
     base::TimeDelta::FromSeconds(1);
-constexpr int16_t kFilterDeviceLostThreshold = -100;
 constexpr base::TimeDelta kFilterDeviceLostTimeout =
     base::TimeDelta::FromSeconds(5);
 constexpr uint8_t kFilterPatternStartPosition = 0;
@@ -54,7 +52,7 @@
       device::BluetoothLowEnergyScanFilter::AdvertisementDataType::kServiceData,
       kFastPairFilterPatternValue);
   auto filter = device::BluetoothLowEnergyScanFilter::Create(
-      kFilterDeviceFoundThreshold, kFilterDeviceLostThreshold,
+      device::BluetoothLowEnergyScanFilter::Range::kNear,
       kFilterDeviceFoundTimeout, kFilterDeviceLostTimeout, {pattern});
   if (!filter) {
     QP_LOG(ERROR) << "Bluetooth Low Energy Scan Session failed to start due to "
diff --git a/ash/quick_pair/scanning/fast_pair/fast_pair_scanner_impl.h b/ash/quick_pair/scanning/fast_pair/fast_pair_scanner_impl.h
index 9d43f23..e939f0b 100644
--- a/ash/quick_pair/scanning/fast_pair/fast_pair_scanner_impl.h
+++ b/ash/quick_pair/scanning/fast_pair/fast_pair_scanner_impl.h
@@ -28,7 +28,6 @@
       public device::BluetoothLowEnergyScanSession::Delegate {
  public:
   FastPairScannerImpl();
-  ~FastPairScannerImpl() override;
   FastPairScannerImpl(const FastPairScanner&) = delete;
   FastPairScannerImpl& operator=(const FastPairScanner&) = delete;
 
@@ -37,6 +36,8 @@
   void RemoveObserver(FastPairScanner::Observer* observer) override;
 
  private:
+  ~FastPairScannerImpl() override;
+
   // device::BluetoothAdapter::Observer
   void DeviceChanged(device::BluetoothAdapter* adapter,
                      device::BluetoothDevice* device) override;
diff --git a/ash/quick_pair/scanning/fast_pair/fast_pair_scanner_unittest.cc b/ash/quick_pair/scanning/fast_pair/fast_pair_scanner_unittest.cc
index 17a252d..7f17501 100644
--- a/ash/quick_pair/scanning/fast_pair/fast_pair_scanner_unittest.cc
+++ b/ash/quick_pair/scanning/fast_pair/fast_pair_scanner_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "ash/quick_pair/common/constants.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "device/bluetooth/bluetooth_adapter_factory.h"
 #include "device/bluetooth/bluetooth_low_energy_scan_filter.h"
@@ -105,7 +106,7 @@
             Invoke(this, &FastPairScannerTest::StartLowEnergyScanSession));
     device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
     EXPECT_CALL(adapter(), AddObserver);
-    scanner_ = std::make_unique<FastPairScannerImpl>();
+    scanner_ = base::MakeRefCounted<FastPairScannerImpl>();
     scanner_observer_ = std::make_unique<FastPairScannerObserver>();
     scanner().AddObserver(scanner_observer_.get());
   }
@@ -154,7 +155,7 @@
 
  protected:
   scoped_refptr<FakeBluetoothAdapter> adapter_;
-  std::unique_ptr<FastPairScannerImpl> scanner_;
+  scoped_refptr<FastPairScannerImpl> scanner_;
   device::MockBluetoothLowEnergyScanSession* mock_scan_session_ = nullptr;
   std::unique_ptr<FastPairScannerObserver> scanner_observer_;
   base::WeakPtr<device::BluetoothLowEnergyScanSession::Delegate> delegate_;
diff --git a/ash/quick_pair/scanning/mock_scanner_broker.cc b/ash/quick_pair/scanning/mock_scanner_broker.cc
index 85c08bc9..f8c97ed 100644
--- a/ash/quick_pair/scanning/mock_scanner_broker.cc
+++ b/ash/quick_pair/scanning/mock_scanner_broker.cc
@@ -5,6 +5,7 @@
 #include "ash/quick_pair/scanning/mock_scanner_broker.h"
 
 #include "ash/quick_pair/common/device.h"
+#include "base/memory/scoped_refptr.h"
 
 namespace ash {
 namespace quick_pair {
@@ -21,12 +22,12 @@
   observers_.RemoveObserver(observer);
 }
 
-void MockScannerBroker::NotifyDeviceFound(const Device& device) {
+void MockScannerBroker::NotifyDeviceFound(scoped_refptr<Device> device) {
   for (auto& obs : observers_)
     obs.OnDeviceFound(device);
 }
 
-void MockScannerBroker::NotifyDeviceLost(const Device& device) {
+void MockScannerBroker::NotifyDeviceLost(scoped_refptr<Device> device) {
   for (auto& obs : observers_)
     obs.OnDeviceLost(device);
 }
diff --git a/ash/quick_pair/scanning/mock_scanner_broker.h b/ash/quick_pair/scanning/mock_scanner_broker.h
index 309b1f6..bdc27f0 100644
--- a/ash/quick_pair/scanning/mock_scanner_broker.h
+++ b/ash/quick_pair/scanning/mock_scanner_broker.h
@@ -27,8 +27,8 @@
 
   void AddObserver(Observer* observer) override;
   void RemoveObserver(Observer* observer) override;
-  void NotifyDeviceFound(const Device& device);
-  void NotifyDeviceLost(const Device& device);
+  void NotifyDeviceFound(scoped_refptr<Device> device);
+  void NotifyDeviceLost(scoped_refptr<Device> device);
 
  private:
   base::ObserverList<Observer> observers_;
diff --git a/ash/quick_pair/scanning/scanner_broker.h b/ash/quick_pair/scanning/scanner_broker.h
index 02d19f7..ad8a7fa 100644
--- a/ash/quick_pair/scanning/scanner_broker.h
+++ b/ash/quick_pair/scanning/scanner_broker.h
@@ -5,13 +5,14 @@
 #ifndef ASH_QUICK_PAIR_SCANNING_SCANNER_BROKER_H_
 #define ASH_QUICK_PAIR_SCANNING_SCANNER_BROKER_H_
 
-#include "ash/quick_pair/common/device.h"
 #include "ash/quick_pair/common/protocol.h"
 #include "base/observer_list_types.h"
 
 namespace ash {
 namespace quick_pair {
 
+struct Device;
+
 // The ScannerBroker is the entry point for the Scanning component in the Quick
 // Pair system. It is responsible for brokering the start/stop scanning calls
 // to the correct concrete Scanner implementation, and exposing an observer
@@ -20,8 +21,8 @@
  public:
   class Observer : public base::CheckedObserver {
    public:
-    virtual void OnDeviceFound(const Device& device) = 0;
-    virtual void OnDeviceLost(const Device& device) = 0;
+    virtual void OnDeviceFound(scoped_refptr<Device> device) = 0;
+    virtual void OnDeviceLost(scoped_refptr<Device> device) = 0;
   };
 
   virtual ~ScannerBroker() = default;
diff --git a/ash/quick_pair/scanning/scanner_broker_impl.cc b/ash/quick_pair/scanning/scanner_broker_impl.cc
index f34f08f88..84b892f 100644
--- a/ash/quick_pair/scanning/scanner_broker_impl.cc
+++ b/ash/quick_pair/scanning/scanner_broker_impl.cc
@@ -4,17 +4,43 @@
 
 #include "ash/quick_pair/scanning/scanner_broker_impl.h"
 
+#include <memory>
+
 #include "ash/quick_pair/common/device.h"
 #include "ash/quick_pair/common/logging.h"
 #include "ash/quick_pair/common/protocol.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/check.h"
+#include "base/memory/scoped_refptr.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
 
 namespace ash {
 namespace quick_pair {
 
-ScannerBrokerImpl::ScannerBrokerImpl() = default;
+ScannerBrokerImpl::ScannerBrokerImpl() {
+  device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
+      &ScannerBrokerImpl::OnGetAdapter, weak_pointer_factory_.GetWeakPtr()));
+}
 
 ScannerBrokerImpl::~ScannerBrokerImpl() = default;
 
+void ScannerBrokerImpl::OnGetAdapter(
+    scoped_refptr<device::BluetoothAdapter> adapter) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  adapter_ = adapter;
+
+  if (start_scanning_on_adapter_callbacks_.empty())
+    return;
+
+  QP_LOG(INFO) << __func__ << ": Running saved callbacks.";
+
+  for (auto& callback : start_scanning_on_adapter_callbacks_)
+    std::move(callback).Run();
+
+  start_scanning_on_adapter_callbacks_.clear();
+}
+
 void ScannerBrokerImpl::AddObserver(Observer* observer) {
   observers_.AddObserver(observer);
 }
@@ -24,6 +50,18 @@
 }
 
 void ScannerBrokerImpl::StartScanning(Protocol protocol) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  QP_LOG(INFO) << __func__ << ": protocol=" << protocol;
+
+  if (!adapter_) {
+    QP_LOG(INFO) << __func__ << ": No adapter yet, saving callback for later.";
+
+    start_scanning_on_adapter_callbacks_.push_back(
+        base::BindOnce(&ScannerBrokerImpl::StartScanning,
+                       weak_pointer_factory_.GetWeakPtr(), protocol));
+    return;
+  }
+
   switch (protocol) {
     case Protocol::kFastPair:
       StartFastPairScanning();
@@ -32,6 +70,8 @@
 }
 
 void ScannerBrokerImpl::StopScanning(Protocol protocol) {
+  QP_LOG(INFO) << __func__ << ": protocol=" << protocol;
+
   switch (protocol) {
     case Protocol::kFastPair:
       StopFastPairScanning();
@@ -47,13 +87,17 @@
   QP_LOG(INFO) << "Stoping Fast Pair Scanning.";
 }
 
-void ScannerBrokerImpl::NotifyDeviceFound(const Device& device) {
+void ScannerBrokerImpl::NotifyDeviceFound(scoped_refptr<Device> device) {
+  QP_LOG(INFO) << __func__ << ": device.metadata_id=" << device->metadata_id;
+
   for (auto& observer : observers_) {
     observer.OnDeviceFound(device);
   }
 }
 
-void ScannerBrokerImpl::NotifyDeviceLost(const Device& device) {
+void ScannerBrokerImpl::NotifyDeviceLost(scoped_refptr<Device> device) {
+  QP_LOG(INFO) << __func__ << ": device.metadata_id=" << device->metadata_id;
+
   for (auto& observer : observers_) {
     observer.OnDeviceLost(device);
   }
diff --git a/ash/quick_pair/scanning/scanner_broker_impl.h b/ash/quick_pair/scanning/scanner_broker_impl.h
index 568c92a..3261eec 100644
--- a/ash/quick_pair/scanning/scanner_broker_impl.h
+++ b/ash/quick_pair/scanning/scanner_broker_impl.h
@@ -5,13 +5,23 @@
 #ifndef ASH_QUICK_PAIR_SCANNING_SCANNER_BROKER_IMPL_H_
 #define ASH_QUICK_PAIR_SCANNING_SCANNER_BROKER_IMPL_H_
 
-#include "ash/quick_pair/common/device.h"
+#include <memory>
 #include "ash/quick_pair/scanning/scanner_broker.h"
+#include "base/callback_forward.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "base/sequence_checker.h"
+
+namespace device {
+class BluetoothAdapter;
+}  // namespace device
 
 namespace ash {
 namespace quick_pair {
 
+struct Device;
+
 class ScannerBrokerImpl : public ScannerBroker {
  public:
   ScannerBrokerImpl();
@@ -26,13 +36,18 @@
   void StopScanning(Protocol protocol) override;
 
  private:
+  void OnGetAdapter(scoped_refptr<device::BluetoothAdapter> adapter);
   void StartFastPairScanning();
   void StopFastPairScanning();
 
-  void NotifyDeviceFound(const Device& device);
-  void NotifyDeviceLost(const Device& device);
+  void NotifyDeviceFound(scoped_refptr<Device> device);
+  void NotifyDeviceLost(scoped_refptr<Device> device);
 
+  SEQUENCE_CHECKER(sequence_checker_);
+  std::vector<base::OnceClosure> start_scanning_on_adapter_callbacks_;
+  scoped_refptr<device::BluetoothAdapter> adapter_;
   base::ObserverList<Observer> observers_;
+  base::WeakPtrFactory<ScannerBrokerImpl> weak_pointer_factory_{this};
 };
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.cc b/ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.cc
index ba2f91b..5bb1d1b 100644
--- a/ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.cc
+++ b/ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.cc
@@ -17,7 +17,9 @@
 
 namespace {
 
-const char kNotifierFastPair[] = "ash.fastpair";
+const message_center::NotifierId kNotifierFastPair =
+    message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
+                               "ash.fastpair");
 const char kFastPairErrorNotificationId[] =
     "cros_fast_pair_error_notification_id";
 const char kFastPairDiscoveryNotificationId[] =
@@ -32,7 +34,12 @@
 // Creates an empty Fast Pair notification with the given id and uses the
 // Bluetooth icon and FastPair notifierID.
 std::unique_ptr<message_center::Notification> CreateNotification(
-    const std::string& id) {
+    const std::string& id,
+    message_center::SystemNotificationWarningLevel warning_level) {
+  // Remove any existing Fast Pair notifications so only one appears at a time,
+  // since there isn't a case where all of them should be showing.
+  MessageCenter::Get()->RemoveNotificationsForNotifierId(kNotifierFastPair);
+
   std::unique_ptr<message_center::Notification> notification =
       ash::CreateSystemNotification(
           /*type=*/message_center::NOTIFICATION_TYPE_SIMPLE,
@@ -40,15 +47,11 @@
           /*title=*/std::u16string(),
           /*message=*/std::u16string(),
           /*display_source=*/std::u16string(), /*origin_url=*/GURL(),
-          /*notifier_id=*/
-          message_center::NotifierId(
-              message_center::NotifierType::SYSTEM_COMPONENT,
-              kNotifierFastPair),
+          /*notifier_id=*/kNotifierFastPair,
           /*optional_fields=*/{},
           /*delegate=*/nullptr,
           /*small_image=*/ash::kNotificationBluetoothIcon,
-          /*warning_level=*/
-          message_center::SystemNotificationWarningLevel::NORMAL);
+          /*warning_level=*/warning_level);
 
   notification->set_never_timeout(true);
   notification->set_priority(
@@ -102,11 +105,13 @@
     base::OnceClosure launch_bluetooth_pairing,
     base::OnceCallback<void(bool)> on_close) {
   std::unique_ptr<message_center::Notification> error_notification =
-      CreateNotification(kFastPairErrorNotificationId);
+      CreateNotification(
+          kFastPairErrorNotificationId,
+          message_center::SystemNotificationWarningLevel::CRITICAL_WARNING);
   error_notification->set_title(l10n_util::GetStringFUTF16(
       IDS_FAST_PAIR_CONNECTION_ERROR_TITLE, device_name));
-  error_notification->set_message(l10n_util::GetStringFUTF16(
-      IDS_FAST_PAIR_CONNECTION_ERROR_MESSAGE, std::u16string()));
+  error_notification->set_message(
+      l10n_util::GetStringUTF16(IDS_FAST_PAIR_CONNECTION_ERROR_MESSAGE));
 
   message_center::ButtonInfo settings_button(
       l10n_util::GetStringUTF16(IDS_FAST_PAIR_SETTINGS_BUTTON));
@@ -114,8 +119,6 @@
 
   error_notification->set_delegate(base::MakeRefCounted<NotificationDelegate>(
       std::move(launch_bluetooth_pairing), std::move(on_close)));
-  error_notification->set_system_notification_warning_level(
-      message_center::SystemNotificationWarningLevel::CRITICAL_WARNING);
   error_notification->set_image(device_image);
 
   MessageCenter::Get()->AddNotification(std::move(error_notification));
@@ -127,7 +130,9 @@
     base::OnceClosure on_connect_clicked,
     base::OnceCallback<void(bool)> on_close) {
   std::unique_ptr<message_center::Notification> discovery_notification =
-      CreateNotification(kFastPairDiscoveryNotificationId);
+      CreateNotification(
+          kFastPairDiscoveryNotificationId,
+          message_center::SystemNotificationWarningLevel::NORMAL);
   discovery_notification->set_title(l10n_util::GetStringFUTF16(
       IDS_FAST_PAIR_DISCOVERY_NOTIFICATION_TITLE, device_name));
   discovery_notification->set_message(l10n_util::GetStringFUTF16(
@@ -151,7 +156,9 @@
     base::OnceClosure on_cancel_clicked,
     base::OnceCallback<void(bool)> on_close) {
   std::unique_ptr<message_center::Notification> pairing_notification =
-      CreateNotification(kFastPairPairingNotificationId);
+      CreateNotification(
+          kFastPairPairingNotificationId,
+          message_center::SystemNotificationWarningLevel::NORMAL);
   pairing_notification->set_title(l10n_util::GetStringFUTF16(
       IDS_FAST_PAIR_PAIRING_NOTIFICATION_TITLE, device_name));
 
diff --git a/ash/quick_pair/ui/fast_pair/fast_pair_presenter.cc b/ash/quick_pair/ui/fast_pair/fast_pair_presenter.cc
index caaf015a..3a59aa4 100644
--- a/ash/quick_pair/ui/fast_pair/fast_pair_presenter.cc
+++ b/ash/quick_pair/ui/fast_pair/fast_pair_presenter.cc
@@ -6,6 +6,7 @@
 
 #include <string>
 
+#include "ash/quick_pair/common/device.h"
 #include "ash/quick_pair/ui/actions.h"
 #include "base/bind.h"
 #include "base/callback.h"
@@ -19,14 +20,17 @@
 
 FastPairPresenter::~FastPairPresenter() = default;
 
-void FastPairPresenter::ShowDiscovery(const Device& device,
+void FastPairPresenter::ShowDiscovery(scoped_refptr<Device> device,
                                       DiscoveryCallback callback) {
+  auto split_callback = base::SplitOnceCallback(std::move(callback));
   notification_controller_->ShowDiscoveryNotification(
-      base::ASCIIToUTF16(device.metadata_id), gfx::Image(),
+      base::ASCIIToUTF16(device->metadata_id), gfx::Image(),
       base::BindOnce(&FastPairPresenter::OnDiscoveryClicked,
-                     weak_pointer_factory_.GetWeakPtr(), std::move(callback)),
+                     weak_pointer_factory_.GetWeakPtr(),
+                     std::move(split_callback.first)),
       base::BindOnce(&FastPairPresenter::OnDiscoveryDismissed,
-                     weak_pointer_factory_.GetWeakPtr(), std::move(callback)));
+                     weak_pointer_factory_.GetWeakPtr(),
+                     std::move(split_callback.second)));
 }
 
 void FastPairPresenter::OnDiscoveryClicked(DiscoveryCallback callback) {
@@ -39,20 +43,23 @@
                                          : DiscoveryAction::kDismissed);
 }
 
-void FastPairPresenter::ShowPairing(const Device& device) {
+void FastPairPresenter::ShowPairing(scoped_refptr<Device> device) {
   notification_controller_->ShowPairingNotification(
-      base::ASCIIToUTF16(device.metadata_id), gfx::Image(), base::DoNothing(),
+      base::ASCIIToUTF16(device->metadata_id), gfx::Image(), base::DoNothing(),
       base::DoNothing());
 }
 
-void FastPairPresenter::ShowPairingFailed(const Device& device,
+void FastPairPresenter::ShowPairingFailed(scoped_refptr<Device> device,
                                           PairingFailedCallback callback) {
+  auto split_callback = base::SplitOnceCallback(std::move(callback));
   notification_controller_->ShowErrorNotification(
-      base::ASCIIToUTF16(device.metadata_id), gfx::Image(),
+      base::ASCIIToUTF16(device->metadata_id), gfx::Image(),
       base::BindOnce(&FastPairPresenter::OnNavigateToSettings,
-                     weak_pointer_factory_.GetWeakPtr(), std::move(callback)),
+                     weak_pointer_factory_.GetWeakPtr(),
+                     std::move(split_callback.first)),
       base::BindOnce(&FastPairPresenter::OnPairingFailedDismissed,
-                     weak_pointer_factory_.GetWeakPtr(), std::move(callback)));
+                     weak_pointer_factory_.GetWeakPtr(),
+                     std::move(split_callback.second)));
 }
 
 void FastPairPresenter::OnNavigateToSettings(PairingFailedCallback callback) {
@@ -66,10 +73,10 @@
 }
 
 void FastPairPresenter::ShowAssociateAccount(
-    const Device& device,
+    scoped_refptr<Device> device,
     AssociateAccountCallback callback) {}
 
-void FastPairPresenter::ShowCompanionApp(const Device& device,
+void FastPairPresenter::ShowCompanionApp(scoped_refptr<Device> device,
                                          CompanionAppCallback callback) {}
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/ui/fast_pair/fast_pair_presenter.h b/ash/quick_pair/ui/fast_pair/fast_pair_presenter.h
index 13fe722..11292f6 100644
--- a/ash/quick_pair/ui/fast_pair/fast_pair_presenter.h
+++ b/ash/quick_pair/ui/fast_pair/fast_pair_presenter.h
@@ -6,7 +6,6 @@
 #define ASH_QUICK_PAIR_UI_FAST_PAIR_FAST_PAIR_PRESENTER_H_
 
 #include <memory>
-#include "ash/quick_pair/common/device.h"
 #include "ash/quick_pair/ui/actions.h"
 #include "ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.h"
 #include "base/callback.h"
@@ -15,6 +14,8 @@
 namespace ash {
 namespace quick_pair {
 
+struct Device;
+
 using DiscoveryCallback = base::OnceCallback<void(DiscoveryAction)>;
 using PairingFailedCallback = base::OnceCallback<void(PairingFailedAction)>;
 using AssociateAccountCallback =
@@ -28,12 +29,14 @@
   FastPairPresenter& operator=(const FastPairPresenter&) = delete;
   ~FastPairPresenter();
 
-  void ShowDiscovery(const Device& device, DiscoveryCallback callback);
-  void ShowPairing(const Device& device);
-  void ShowPairingFailed(const Device& device, PairingFailedCallback callback);
-  void ShowAssociateAccount(const Device& device,
+  void ShowDiscovery(scoped_refptr<Device> device, DiscoveryCallback callback);
+  void ShowPairing(scoped_refptr<Device> device);
+  void ShowPairingFailed(scoped_refptr<Device> device,
+                         PairingFailedCallback callback);
+  void ShowAssociateAccount(scoped_refptr<Device> device,
                             AssociateAccountCallback callback);
-  void ShowCompanionApp(const Device& device, CompanionAppCallback callback);
+  void ShowCompanionApp(scoped_refptr<Device> device,
+                        CompanionAppCallback callback);
 
  private:
   void OnDiscoveryClicked(DiscoveryCallback action_callback);
diff --git a/ash/quick_pair/ui/mock_ui_broker.cc b/ash/quick_pair/ui/mock_ui_broker.cc
index ba0b40e..0985925 100644
--- a/ash/quick_pair/ui/mock_ui_broker.cc
+++ b/ash/quick_pair/ui/mock_ui_broker.cc
@@ -6,6 +6,7 @@
 
 #include "ash/quick_pair/common/device.h"
 #include "ash/quick_pair/ui/actions.h"
+#include "base/memory/scoped_refptr.h"
 
 namespace ash {
 namespace quick_pair {
@@ -22,25 +23,25 @@
   observers_.RemoveObserver(observer);
 }
 
-void MockUIBroker::NotifyDiscoveryAction(const Device& device,
+void MockUIBroker::NotifyDiscoveryAction(scoped_refptr<Device> device,
                                          DiscoveryAction action) {
   for (auto& obs : observers_)
     obs.OnDiscoveryAction(device, action);
 }
 
-void MockUIBroker::NotifyCompanionAppAction(const Device& device,
+void MockUIBroker::NotifyCompanionAppAction(scoped_refptr<Device> device,
                                             CompanionAppAction action) {
   for (auto& obs : observers_)
     obs.OnCompanionAppAction(device, action);
 }
 
-void MockUIBroker::NotifyPairingFailedAction(const Device& device,
+void MockUIBroker::NotifyPairingFailedAction(scoped_refptr<Device> device,
                                              PairingFailedAction action) {
   for (auto& obs : observers_)
     obs.OnPairingFailureAction(device, action);
 }
 
-void MockUIBroker::NotifyAssociateAccountAction(const Device& device,
+void MockUIBroker::NotifyAssociateAccountAction(scoped_refptr<Device> device,
                                                 AssociateAccountAction action) {
   for (auto& obs : observers_)
     obs.OnAssociateAccountAction(device, action);
diff --git a/ash/quick_pair/ui/mock_ui_broker.h b/ash/quick_pair/ui/mock_ui_broker.h
index 0c22653..d68d722 100644
--- a/ash/quick_pair/ui/mock_ui_broker.h
+++ b/ash/quick_pair/ui/mock_ui_broker.h
@@ -22,20 +22,21 @@
   MockUIBroker& operator=(const MockUIBroker&) = delete;
   ~MockUIBroker() override;
 
-  MOCK_METHOD(void, ShowDiscovery, (const Device&), (override));
-  MOCK_METHOD(void, ShowPairing, (const Device&), (override));
-  MOCK_METHOD(void, ShowPairingFailed, (const Device&), (override));
-  MOCK_METHOD(void, ShowAssociateAccount, (const Device&), (override));
-  MOCK_METHOD(void, ShowCompanionApp, (const Device&), (override));
+  MOCK_METHOD(void, ShowDiscovery, (scoped_refptr<Device>), (override));
+  MOCK_METHOD(void, ShowPairing, (scoped_refptr<Device>), (override));
+  MOCK_METHOD(void, ShowPairingFailed, (scoped_refptr<Device>), (override));
+  MOCK_METHOD(void, ShowAssociateAccount, (scoped_refptr<Device>), (override));
+  MOCK_METHOD(void, ShowCompanionApp, (scoped_refptr<Device>), (override));
 
   void AddObserver(Observer* observer) override;
   void RemoveObserver(Observer* observer) override;
-  void NotifyDiscoveryAction(const Device& device, DiscoveryAction action);
-  void NotifyCompanionAppAction(const Device& device,
+  void NotifyDiscoveryAction(scoped_refptr<Device> device,
+                             DiscoveryAction action);
+  void NotifyCompanionAppAction(scoped_refptr<Device> device,
                                 CompanionAppAction action);
-  void NotifyPairingFailedAction(const Device& device,
+  void NotifyPairingFailedAction(scoped_refptr<Device> device,
                                  PairingFailedAction action);
-  void NotifyAssociateAccountAction(const Device& device,
+  void NotifyAssociateAccountAction(scoped_refptr<Device> device,
                                     AssociateAccountAction action);
 
  private:
diff --git a/ash/quick_pair/ui/ui_broker.h b/ash/quick_pair/ui/ui_broker.h
index b1cec42..3d4efb0 100644
--- a/ash/quick_pair/ui/ui_broker.h
+++ b/ash/quick_pair/ui/ui_broker.h
@@ -5,13 +5,14 @@
 #ifndef ASH_QUICK_PAIR_UI_UI_BROKER_H_
 #define ASH_QUICK_PAIR_UI_UI_BROKER_H_
 
-#include "ash/quick_pair/common/device.h"
 #include "ash/quick_pair/ui/actions.h"
 #include "base/observer_list_types.h"
 
 namespace ash {
 namespace quick_pair {
 
+struct Device;
+
 // The UIBroker is the entry point for the UI component in the Quick Pair
 // system. It is responsible for brokering the 'show UI' calls to the correct
 // Presenter implementation, and exposing user actions taken on that UI.
@@ -19,13 +20,13 @@
  public:
   class Observer : public base::CheckedObserver {
    public:
-    virtual void OnDiscoveryAction(const Device& device,
+    virtual void OnDiscoveryAction(scoped_refptr<Device> device,
                                    DiscoveryAction action) = 0;
-    virtual void OnCompanionAppAction(const Device& device,
+    virtual void OnCompanionAppAction(scoped_refptr<Device> device,
                                       CompanionAppAction action) = 0;
-    virtual void OnPairingFailureAction(const Device& device,
+    virtual void OnPairingFailureAction(scoped_refptr<Device> device,
                                         PairingFailedAction action) = 0;
-    virtual void OnAssociateAccountAction(const Device& device,
+    virtual void OnAssociateAccountAction(scoped_refptr<Device> device,
                                           AssociateAccountAction action) = 0;
   };
 
@@ -33,11 +34,11 @@
 
   virtual void AddObserver(Observer* observer) = 0;
   virtual void RemoveObserver(Observer* observer) = 0;
-  virtual void ShowDiscovery(const Device& device) = 0;
-  virtual void ShowPairing(const Device& device) = 0;
-  virtual void ShowPairingFailed(const Device& device) = 0;
-  virtual void ShowAssociateAccount(const Device& device) = 0;
-  virtual void ShowCompanionApp(const Device& device) = 0;
+  virtual void ShowDiscovery(scoped_refptr<Device> device) = 0;
+  virtual void ShowPairing(scoped_refptr<Device> device) = 0;
+  virtual void ShowPairingFailed(scoped_refptr<Device> device) = 0;
+  virtual void ShowAssociateAccount(scoped_refptr<Device> device) = 0;
+  virtual void ShowCompanionApp(scoped_refptr<Device> device) = 0;
 };
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/ui/ui_broker_impl.cc b/ash/quick_pair/ui/ui_broker_impl.cc
index 0d9276a2..462e959 100644
--- a/ash/quick_pair/ui/ui_broker_impl.cc
+++ b/ash/quick_pair/ui/ui_broker_impl.cc
@@ -28,77 +28,73 @@
   observers_.RemoveObserver(observer);
 }
 
-void UIBrokerImpl::ShowDiscovery(const Device& device) {
-  switch (device.protocol) {
+void UIBrokerImpl::ShowDiscovery(scoped_refptr<Device> device) {
+  switch (device->protocol) {
     case Protocol::kFastPair:
       fast_pair_presenter_->ShowDiscovery(
-          device,
-          base::BindOnce(&UIBrokerImpl::NotifyDiscoveryAction,
-                         weak_pointer_factory_.GetWeakPtr(), std::ref(device)));
+          device, base::BindOnce(&UIBrokerImpl::NotifyDiscoveryAction,
+                                 weak_pointer_factory_.GetWeakPtr(), device));
       break;
   }
 }
 
-void UIBrokerImpl::ShowPairing(const Device& device) {
-  switch (device.protocol) {
+void UIBrokerImpl::ShowPairing(scoped_refptr<Device> device) {
+  switch (device->protocol) {
     case Protocol::kFastPair:
-      fast_pair_presenter_->ShowPairing(device);
+      fast_pair_presenter_->ShowPairing(std::move(device));
       break;
   }
 }
 
-void UIBrokerImpl::ShowPairingFailed(const Device& device) {
-  switch (device.protocol) {
+void UIBrokerImpl::ShowPairingFailed(scoped_refptr<Device> device) {
+  switch (device->protocol) {
     case Protocol::kFastPair:
       fast_pair_presenter_->ShowPairingFailed(
-          device,
-          base::BindOnce(&UIBrokerImpl::NotifyPairingFailedAction,
-                         weak_pointer_factory_.GetWeakPtr(), std::ref(device)));
+          device, base::BindOnce(&UIBrokerImpl::NotifyPairingFailedAction,
+                                 weak_pointer_factory_.GetWeakPtr(), device));
       break;
   }
 }
 
-void UIBrokerImpl::ShowAssociateAccount(const Device& device) {
-  switch (device.protocol) {
+void UIBrokerImpl::ShowAssociateAccount(scoped_refptr<Device> device) {
+  switch (device->protocol) {
     case Protocol::kFastPair:
       fast_pair_presenter_->ShowAssociateAccount(
-          device,
-          base::BindOnce(&UIBrokerImpl::NotifyAssociateAccountAction,
-                         weak_pointer_factory_.GetWeakPtr(), std::ref(device)));
+          device, base::BindOnce(&UIBrokerImpl::NotifyAssociateAccountAction,
+                                 weak_pointer_factory_.GetWeakPtr(), device));
       break;
   }
 }
 
-void UIBrokerImpl::ShowCompanionApp(const Device& device) {
-  switch (device.protocol) {
+void UIBrokerImpl::ShowCompanionApp(scoped_refptr<Device> device) {
+  switch (device->protocol) {
     case Protocol::kFastPair:
       fast_pair_presenter_->ShowCompanionApp(
-          device,
-          base::BindOnce(&UIBrokerImpl::NotifyCompanionAppAction,
-                         weak_pointer_factory_.GetWeakPtr(), std::ref(device)));
+          device, base::BindOnce(&UIBrokerImpl::NotifyCompanionAppAction,
+                                 weak_pointer_factory_.GetWeakPtr(), device));
       break;
   }
 }
 
-void UIBrokerImpl::NotifyDiscoveryAction(const Device& device,
+void UIBrokerImpl::NotifyDiscoveryAction(scoped_refptr<Device> device,
                                          DiscoveryAction action) {
   for (auto& observer : observers_)
     observer.OnDiscoveryAction(device, action);
 }
 
-void UIBrokerImpl::NotifyPairingFailedAction(const Device& device,
+void UIBrokerImpl::NotifyPairingFailedAction(scoped_refptr<Device> device,
                                              PairingFailedAction action) {
   for (auto& observer : observers_)
     observer.OnPairingFailureAction(device, action);
 }
 
-void UIBrokerImpl::NotifyAssociateAccountAction(const Device& device,
+void UIBrokerImpl::NotifyAssociateAccountAction(scoped_refptr<Device> device,
                                                 AssociateAccountAction action) {
   for (auto& observer : observers_)
     observer.OnAssociateAccountAction(device, action);
 }
 
-void UIBrokerImpl::NotifyCompanionAppAction(const Device& device,
+void UIBrokerImpl::NotifyCompanionAppAction(scoped_refptr<Device> device,
                                             CompanionAppAction action) {
   for (auto& observer : observers_)
     observer.OnCompanionAppAction(device, action);
diff --git a/ash/quick_pair/ui/ui_broker_impl.h b/ash/quick_pair/ui/ui_broker_impl.h
index a599868..c419087 100644
--- a/ash/quick_pair/ui/ui_broker_impl.h
+++ b/ash/quick_pair/ui/ui_broker_impl.h
@@ -16,6 +16,7 @@
 namespace quick_pair {
 
 class FastPairPresenter;
+struct Device;
 
 class COMPONENT_EXPORT(QUICK_PAIR_UI) UIBrokerImpl : public UIBroker {
  public:
@@ -27,19 +28,20 @@
   // UIBroker:
   void AddObserver(Observer* observer) override;
   void RemoveObserver(Observer* observer) override;
-  void ShowDiscovery(const Device& device) override;
-  void ShowPairing(const Device& device) override;
-  void ShowPairingFailed(const Device& device) override;
-  void ShowAssociateAccount(const Device& device) override;
-  void ShowCompanionApp(const Device& device) override;
+  void ShowDiscovery(scoped_refptr<Device> device) override;
+  void ShowPairing(scoped_refptr<Device> device) override;
+  void ShowPairingFailed(scoped_refptr<Device> device) override;
+  void ShowAssociateAccount(scoped_refptr<Device> device) override;
+  void ShowCompanionApp(scoped_refptr<Device> device) override;
 
  private:
-  void NotifyDiscoveryAction(const Device& device, DiscoveryAction action);
-  void NotifyPairingFailedAction(const Device& device,
+  void NotifyDiscoveryAction(scoped_refptr<Device> device,
+                             DiscoveryAction action);
+  void NotifyPairingFailedAction(scoped_refptr<Device> device,
                                  PairingFailedAction action);
-  void NotifyAssociateAccountAction(const Device& device,
+  void NotifyAssociateAccountAction(scoped_refptr<Device> device,
                                     AssociateAccountAction action);
-  void NotifyCompanionAppAction(const Device& device,
+  void NotifyCompanionAppAction(scoped_refptr<Device> device,
                                 CompanionAppAction action);
 
   std::unique_ptr<FastPairPresenter> fast_pair_presenter_;
diff --git a/ash/style/ash_color_provider.cc b/ash/style/ash_color_provider.cc
index 2c97750..21c8da32 100644
--- a/ash/style/ash_color_provider.cc
+++ b/ash/style/ash_color_provider.cc
@@ -27,6 +27,7 @@
 #include "ui/gfx/color_utils.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/vector_icon_utils.h"
+#include "ui/views/animation/ink_drop_host_view.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/button/label_button.h"
@@ -294,6 +295,22 @@
                      GetDefaultSizeOfVectorIcon(icon));
 }
 
+void AshColorProvider::DecorateInkDrop(views::InkDropHost* host,
+                                       int ink_drop_config_flags,
+                                       SkColor bg_color) {
+  const AshColorProvider::RippleAttributes ripple_attributes =
+      GetRippleAttributes(bg_color);
+
+  if (ink_drop_config_flags & kConfigBaseColor)
+    host->SetBaseColor(ripple_attributes.base_color);
+
+  if (ink_drop_config_flags & kConfigVisibleOpacity)
+    host->SetVisibleOpacity(ripple_attributes.inkdrop_opacity);
+
+  if (ink_drop_config_flags & kConfigHighlightOpacity)
+    host->SetHighlightOpacity(ripple_attributes.highlight_opacity);
+}
+
 void AshColorProvider::DecorateIconButton(views::ImageButton* button,
                                           const gfx::VectorIcon& icon,
                                           bool toggled,
diff --git a/ash/style/ash_color_provider.h b/ash/style/ash_color_provider.h
index 5095089..f2ef8a0 100644
--- a/ash/style/ash_color_provider.h
+++ b/ash/style/ash_color_provider.h
@@ -19,6 +19,7 @@
 
 namespace views {
 class ImageButton;
+class InkDropHost;
 class LabelButton;
 }  // namespace views
 
@@ -101,6 +102,18 @@
   void DecorateFloatingIconButton(views::ImageButton* button,
                                   const gfx::VectorIcon& icon);
 
+  // Decorates the ink drop managed by `host`. `ink_drop_config_flags` is a
+  // bitmask which specifies the ink drop attributes to modify. `bg_color` is
+  // the background color of the UI element that wants to show ink drop.
+  enum InkDropConfigParam {
+    kConfigBaseColor = 1,
+    kConfigVisibleOpacity = 1 << 1,
+    kConfigHighlightOpacity = 1 << 2
+  };
+  void DecorateInkDrop(views::InkDropHost* host,
+                       int ink_drop_config_flags,
+                       SkColor bg_color = gfx::kPlaceholderColor);
+
   // Whether the system color mode is themed, by default is true. If true, the
   // background color will be calculated based on extracted wallpaper color.
   bool IsThemed() const;
diff --git a/ash/system/accessibility/autoclick_menu_view.cc b/ash/system/accessibility/autoclick_menu_view.cc
index f4f6a76..98f6a6d 100644
--- a/ash/system/accessibility/autoclick_menu_view.cc
+++ b/ash/system/accessibility/autoclick_menu_view.cc
@@ -44,96 +44,95 @@
   views::Builder<AutoclickMenuView>(this)
       .SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kEnd)
       .AddChildren(
-          {views::Builder<views::BoxLayoutView>()
-               .SetInsideBorderInsets(kUnifiedMenuItemPadding)
-               .SetBetweenChildSpacing(kUnifiedTopShortcutSpacing)
-               .AddChildren(
-                   {views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&left_click_button_)
-                        .SetID(static_cast<int>(ButtonId::kLeftClick))
-                        .SetVectorIcon(kAutoclickLeftClickIcon)
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_AUTOCLICK_OPTION_LEFT_CLICK))
-                        .SetCallback(base::BindRepeating(
-                            &AutoclickMenuView::OnAutoclickButtonPressed,
-                            base::Unretained(this),
-                            base::Unretained(left_click_button_))),
-                    views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&right_click_button_)
-                        .SetID(static_cast<int>(ButtonId::kRightClick))
-                        .SetVectorIcon(kAutoclickRightClickIcon)
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_AUTOCLICK_OPTION_RIGHT_CLICK))
-                        .SetCallback(base::BindRepeating(
-                            &AutoclickMenuView::OnAutoclickButtonPressed,
-                            base::Unretained(this),
-                            base::Unretained(right_click_button_))),
-                    views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&double_click_button_)
-                        .SetID(static_cast<int>(ButtonId::kDoubleClick))
-                        .SetVectorIcon(kAutoclickDoubleClickIcon)
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_AUTOCLICK_OPTION_DOUBLE_CLICK))
-                        .SetCallback(base::BindRepeating(
-                            &AutoclickMenuView::OnAutoclickButtonPressed,
-                            base::Unretained(this),
-                            base::Unretained(double_click_button_))),
-                    views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&drag_button_)
-                        .SetID(static_cast<int>(ButtonId::kDragAndDrop))
-                        .SetVectorIcon(kAutoclickDragIcon)
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_AUTOCLICK_OPTION_DRAG_AND_DROP))
-                        .SetCallback(base::BindRepeating(
-                            &AutoclickMenuView::OnAutoclickButtonPressed,
-                            base::Unretained(this),
-                            base::Unretained(drag_button_))),
-                    views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&scroll_button_)
-                        .SetID(static_cast<int>(ButtonId::kScroll))
-                        .SetVectorIcon(kAutoclickScrollIcon)
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_AUTOCLICK_OPTION_SCROLL))
-                        .SetCallback(base::BindRepeating(
-                            &AutoclickMenuView::OnAutoclickButtonPressed,
-                            base::Unretained(this),
-                            base::Unretained(scroll_button_))),
-                    views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&pause_button_)
-                        .SetID(static_cast<int>(ButtonId::kPause))
-                        .SetVectorIcon(kAutoclickPauseIcon)
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_AUTOCLICK_OPTION_NO_ACTION))
-                        .SetCallback(base::BindRepeating(
-                            &AutoclickMenuView::OnAutoclickButtonPressed,
-                            base::Unretained(this),
-                            base::Unretained(pause_button_)))}),
-           views::Builder<views::Separator>()
-               .SetColor(AshColorProvider::Get()->GetContentLayerColor(
-                   AshColorProvider::ContentLayerType::kSeparatorColor))
-               .SetPreferredHeight(kSeparatorHeight)
-               .SetBorder(views::CreateEmptyBorder(
-                   separator_spacing - kUnifiedTopShortcutSpacing, 0,
-                   separator_spacing, 0)),
-           views::Builder<views::BoxLayoutView>()
-               .SetInsideBorderInsets(gfx::Insets(
-                   0, kPanelPositionButtonPadding, kPanelPositionButtonPadding,
-                   kPanelPositionButtonPadding))
-               .SetBetweenChildSpacing(kPanelPositionButtonPadding)
-               .AddChildren(
-                   {views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&position_button_)
-                        .SetID(static_cast<int>(ButtonId::kPosition))
-                        .SetVectorIcon(kAutoclickPositionBottomLeftIcon)
-                        .SetPreferredSize(gfx::Size(kPanelPositionButtonSize,
-                                                    kPanelPositionButtonSize))
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_AUTOCLICK_OPTION_CHANGE_POSITION))
-                        .SetDrawHighlight(false)
-                        .SetA11yTogglable(false)
-                        .SetCallback(base::BindRepeating(
-                            &AutoclickMenuView::OnPositionButtonPressed,
-                            base::Unretained(this)))})})
+          views::Builder<views::BoxLayoutView>()
+              .SetInsideBorderInsets(kUnifiedMenuItemPadding)
+              .SetBetweenChildSpacing(kUnifiedTopShortcutSpacing)
+              .AddChildren(views::Builder<FloatingMenuButton>()
+                               .CopyAddressTo(&left_click_button_)
+                               .SetID(static_cast<int>(ButtonId::kLeftClick))
+                               .SetVectorIcon(kAutoclickLeftClickIcon)
+                               .SetTooltipText(l10n_util::GetStringUTF16(
+                                   IDS_ASH_AUTOCLICK_OPTION_LEFT_CLICK))
+                               .SetCallback(base::BindRepeating(
+                                   &AutoclickMenuView::OnAutoclickButtonPressed,
+                                   base::Unretained(this),
+                                   base::Unretained(left_click_button_))),
+                           views::Builder<FloatingMenuButton>()
+                               .CopyAddressTo(&right_click_button_)
+                               .SetID(static_cast<int>(ButtonId::kRightClick))
+                               .SetVectorIcon(kAutoclickRightClickIcon)
+                               .SetTooltipText(l10n_util::GetStringUTF16(
+                                   IDS_ASH_AUTOCLICK_OPTION_RIGHT_CLICK))
+                               .SetCallback(base::BindRepeating(
+                                   &AutoclickMenuView::OnAutoclickButtonPressed,
+                                   base::Unretained(this),
+                                   base::Unretained(right_click_button_))),
+                           views::Builder<FloatingMenuButton>()
+                               .CopyAddressTo(&double_click_button_)
+                               .SetID(static_cast<int>(ButtonId::kDoubleClick))
+                               .SetVectorIcon(kAutoclickDoubleClickIcon)
+                               .SetTooltipText(l10n_util::GetStringUTF16(
+                                   IDS_ASH_AUTOCLICK_OPTION_DOUBLE_CLICK))
+                               .SetCallback(base::BindRepeating(
+                                   &AutoclickMenuView::OnAutoclickButtonPressed,
+                                   base::Unretained(this),
+                                   base::Unretained(double_click_button_))),
+                           views::Builder<FloatingMenuButton>()
+                               .CopyAddressTo(&drag_button_)
+                               .SetID(static_cast<int>(ButtonId::kDragAndDrop))
+                               .SetVectorIcon(kAutoclickDragIcon)
+                               .SetTooltipText(l10n_util::GetStringUTF16(
+                                   IDS_ASH_AUTOCLICK_OPTION_DRAG_AND_DROP))
+                               .SetCallback(base::BindRepeating(
+                                   &AutoclickMenuView::OnAutoclickButtonPressed,
+                                   base::Unretained(this),
+                                   base::Unretained(drag_button_))),
+                           views::Builder<FloatingMenuButton>()
+                               .CopyAddressTo(&scroll_button_)
+                               .SetID(static_cast<int>(ButtonId::kScroll))
+                               .SetVectorIcon(kAutoclickScrollIcon)
+                               .SetTooltipText(l10n_util::GetStringUTF16(
+                                   IDS_ASH_AUTOCLICK_OPTION_SCROLL))
+                               .SetCallback(base::BindRepeating(
+                                   &AutoclickMenuView::OnAutoclickButtonPressed,
+                                   base::Unretained(this),
+                                   base::Unretained(scroll_button_))),
+                           views::Builder<FloatingMenuButton>()
+                               .CopyAddressTo(&pause_button_)
+                               .SetID(static_cast<int>(ButtonId::kPause))
+                               .SetVectorIcon(kAutoclickPauseIcon)
+                               .SetTooltipText(l10n_util::GetStringUTF16(
+                                   IDS_ASH_AUTOCLICK_OPTION_NO_ACTION))
+                               .SetCallback(base::BindRepeating(
+                                   &AutoclickMenuView::OnAutoclickButtonPressed,
+                                   base::Unretained(this),
+                                   base::Unretained(pause_button_)))),
+          views::Builder<views::Separator>()
+              .SetColor(AshColorProvider::Get()->GetContentLayerColor(
+                  AshColorProvider::ContentLayerType::kSeparatorColor))
+              .SetPreferredHeight(kSeparatorHeight)
+              .SetBorder(views::CreateEmptyBorder(
+                  separator_spacing - kUnifiedTopShortcutSpacing, 0,
+                  separator_spacing, 0)),
+          views::Builder<views::BoxLayoutView>()
+              .SetInsideBorderInsets(gfx::Insets(0, kPanelPositionButtonPadding,
+                                                 kPanelPositionButtonPadding,
+                                                 kPanelPositionButtonPadding))
+              .SetBetweenChildSpacing(kPanelPositionButtonPadding)
+              .AddChildren(
+                  views::Builder<FloatingMenuButton>()
+                      .CopyAddressTo(&position_button_)
+                      .SetID(static_cast<int>(ButtonId::kPosition))
+                      .SetVectorIcon(kAutoclickPositionBottomLeftIcon)
+                      .SetPreferredSize(gfx::Size(kPanelPositionButtonSize,
+                                                  kPanelPositionButtonSize))
+                      .SetTooltipText(l10n_util::GetStringUTF16(
+                          IDS_ASH_AUTOCLICK_OPTION_CHANGE_POSITION))
+                      .SetDrawHighlight(false)
+                      .SetA11yTogglable(false)
+                      .SetCallback(base::BindRepeating(
+                          &AutoclickMenuView::OnPositionButtonPressed,
+                          base::Unretained(this)))))
       .BuildChildren();
   UpdateEventType(type);
   UpdatePosition(position);
diff --git a/ash/system/accessibility/select_to_speak/select_to_speak_menu_view.cc b/ash/system/accessibility/select_to_speak/select_to_speak_menu_view.cc
index 37d91f9..c785800d 100644
--- a/ash/system/accessibility/select_to_speak/select_to_speak_menu_view.cc
+++ b/ash/system/accessibility/select_to_speak/select_to_speak_menu_view.cc
@@ -80,99 +80,99 @@
   views::Builder<SelectToSpeakMenuView>(this)
       .SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kEnd)
       .AddChildren(
-          {views::Builder<views::BoxLayoutView>()
-               .SetInsideBorderInsets(kUnifiedMenuItemPadding)
-               .SetBetweenChildSpacing(kUnifiedTopShortcutSpacing)
-               .AddChildren(
-                   {views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&prev_paragraph_button_)
-                        .SetID(static_cast<int>(ButtonId::kPrevParagraph))
-                        .SetVectorIcon(kSelectToSpeakPrevParagraphIcon)
-                        .SetFlipCanvasOnPaintForRTLUI(true)
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_SELECT_TO_SPEAK_PREV_PARAGRAPH))
-                        .SetCallback(base::BindRepeating(
-                            &SelectToSpeakMenuView::OnButtonPressed,
-                            base::Unretained(this),
-                            base::Unretained(prev_paragraph_button_))),
-                    views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&prev_sentence_button_)
-                        .SetID(static_cast<int>(ButtonId::kPrevSentence))
-                        .SetVectorIcon(kSelectToSpeakPrevSentenceIcon)
-                        .SetFlipCanvasOnPaintForRTLUI(true)
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_SELECT_TO_SPEAK_PREV_SENTENCE))
-                        .SetCallback(base::BindRepeating(
-                            &SelectToSpeakMenuView::OnButtonPressed,
-                            base::Unretained(this),
-                            base::Unretained(prev_sentence_button_))),
-                    views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&pause_button_)
-                        .SetID(static_cast<int>(ButtonId::kPause))
-                        .SetVectorIcon(kSelectToSpeakPauseIcon)
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_SELECT_TO_SPEAK_PAUSE))
-                        .SetCallback(base::BindRepeating(
-                            &SelectToSpeakMenuView::OnButtonPressed,
-                            base::Unretained(this),
-                            base::Unretained(pause_button_))),
-                    views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&next_sentence_button_)
-                        .SetID(static_cast<int>(ButtonId::kNextSentence))
-                        .SetVectorIcon(kSelectToSpeakNextSentenceIcon)
-                        .SetFlipCanvasOnPaintForRTLUI(true)
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_SELECT_TO_SPEAK_NEXT_SENTENCE))
-                        .SetCallback(base::BindRepeating(
-                            &SelectToSpeakMenuView::OnButtonPressed,
-                            base::Unretained(this),
-                            base::Unretained(next_sentence_button_))),
-                    views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&next_paragraph_button_)
-                        .SetID(static_cast<int>(ButtonId::kNextParagraph))
-                        .SetVectorIcon(kSelectToSpeakNextParagraphIcon)
-                        .SetFlipCanvasOnPaintForRTLUI(true)
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_SELECT_TO_SPEAK_NEXT_PARAGRAPH))
-                        .SetCallback(base::BindRepeating(
-                            &SelectToSpeakMenuView::OnButtonPressed,
-                            base::Unretained(this),
-                            base::Unretained(next_paragraph_button_))),
-                    views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&speed_button_)
-                        .SetID(static_cast<int>(ButtonId::kSpeed))
-                        .SetVectorIcon(kSelectToSpeakReadingSpeedNormalIcon)
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_SELECT_TO_SPEAK_READING_SPEED))
-                        .SetCallback(base::BindRepeating(
-                            &SelectToSpeakMenuView::OnButtonPressed,
-                            base::Unretained(this),
-                            base::Unretained(speed_button_)))}),
-           views::Builder<views::Separator>()
-               .SetColor(AshColorProvider::Get()->GetContentLayerColor(
-                   AshColorProvider::ContentLayerType::kSeparatorColor))
-               .SetPreferredHeight(kSeparatorHeight)
-               .SetBorder(views::CreateEmptyBorder(
-                   separator_spacing - kUnifiedTopShortcutSpacing, 0,
-                   separator_spacing, 0)),
-           views::Builder<views::BoxLayoutView>()
-               .SetInsideBorderInsets(gfx::Insets(0, kStopButtonPadding,
-                                                  kStopButtonPadding,
-                                                  kStopButtonPadding))
-               .SetBetweenChildSpacing(kStopButtonPadding)
-               .AddChildren(
-                   {views::Builder<FloatingMenuButton>()
-                        .CopyAddressTo(&stop_button_)
-                        .SetID(static_cast<int>(ButtonId::kStop))
-                        .SetVectorIcon(kSelectToSpeakStopIcon)
-                        .SetDrawHighlight(false)
-                        .SetPreferredSize(gfx::Size(kButtonSize, kButtonSize))
-                        .SetTooltipText(l10n_util::GetStringUTF16(
-                            IDS_ASH_SELECT_TO_SPEAK_EXIT))
-                        .SetCallback(base::BindRepeating(
-                            &SelectToSpeakMenuView::OnButtonPressed,
-                            base::Unretained(this),
-                            base::Unretained(stop_button_)))})})
+          views::Builder<views::BoxLayoutView>()
+              .SetInsideBorderInsets(kUnifiedMenuItemPadding)
+              .SetBetweenChildSpacing(kUnifiedTopShortcutSpacing)
+              .AddChildren(
+                  views::Builder<FloatingMenuButton>()
+                      .CopyAddressTo(&prev_paragraph_button_)
+                      .SetID(static_cast<int>(ButtonId::kPrevParagraph))
+                      .SetVectorIcon(kSelectToSpeakPrevParagraphIcon)
+                      .SetFlipCanvasOnPaintForRTLUI(true)
+                      .SetTooltipText(l10n_util::GetStringUTF16(
+                          IDS_ASH_SELECT_TO_SPEAK_PREV_PARAGRAPH))
+                      .SetCallback(base::BindRepeating(
+                          &SelectToSpeakMenuView::OnButtonPressed,
+                          base::Unretained(this),
+                          base::Unretained(prev_paragraph_button_))),
+                  views::Builder<FloatingMenuButton>()
+                      .CopyAddressTo(&prev_sentence_button_)
+                      .SetID(static_cast<int>(ButtonId::kPrevSentence))
+                      .SetVectorIcon(kSelectToSpeakPrevSentenceIcon)
+                      .SetFlipCanvasOnPaintForRTLUI(true)
+                      .SetTooltipText(l10n_util::GetStringUTF16(
+                          IDS_ASH_SELECT_TO_SPEAK_PREV_SENTENCE))
+                      .SetCallback(base::BindRepeating(
+                          &SelectToSpeakMenuView::OnButtonPressed,
+                          base::Unretained(this),
+                          base::Unretained(prev_sentence_button_))),
+                  views::Builder<FloatingMenuButton>()
+                      .CopyAddressTo(&pause_button_)
+                      .SetID(static_cast<int>(ButtonId::kPause))
+                      .SetVectorIcon(kSelectToSpeakPauseIcon)
+                      .SetTooltipText(l10n_util::GetStringUTF16(
+                          IDS_ASH_SELECT_TO_SPEAK_PAUSE))
+                      .SetCallback(base::BindRepeating(
+                          &SelectToSpeakMenuView::OnButtonPressed,
+                          base::Unretained(this),
+                          base::Unretained(pause_button_))),
+                  views::Builder<FloatingMenuButton>()
+                      .CopyAddressTo(&next_sentence_button_)
+                      .SetID(static_cast<int>(ButtonId::kNextSentence))
+                      .SetVectorIcon(kSelectToSpeakNextSentenceIcon)
+                      .SetFlipCanvasOnPaintForRTLUI(true)
+                      .SetTooltipText(l10n_util::GetStringUTF16(
+                          IDS_ASH_SELECT_TO_SPEAK_NEXT_SENTENCE))
+                      .SetCallback(base::BindRepeating(
+                          &SelectToSpeakMenuView::OnButtonPressed,
+                          base::Unretained(this),
+                          base::Unretained(next_sentence_button_))),
+                  views::Builder<FloatingMenuButton>()
+                      .CopyAddressTo(&next_paragraph_button_)
+                      .SetID(static_cast<int>(ButtonId::kNextParagraph))
+                      .SetVectorIcon(kSelectToSpeakNextParagraphIcon)
+                      .SetFlipCanvasOnPaintForRTLUI(true)
+                      .SetTooltipText(l10n_util::GetStringUTF16(
+                          IDS_ASH_SELECT_TO_SPEAK_NEXT_PARAGRAPH))
+                      .SetCallback(base::BindRepeating(
+                          &SelectToSpeakMenuView::OnButtonPressed,
+                          base::Unretained(this),
+                          base::Unretained(next_paragraph_button_))),
+                  views::Builder<FloatingMenuButton>()
+                      .CopyAddressTo(&speed_button_)
+                      .SetID(static_cast<int>(ButtonId::kSpeed))
+                      .SetVectorIcon(kSelectToSpeakReadingSpeedNormalIcon)
+                      .SetTooltipText(l10n_util::GetStringUTF16(
+                          IDS_ASH_SELECT_TO_SPEAK_READING_SPEED))
+                      .SetCallback(base::BindRepeating(
+                          &SelectToSpeakMenuView::OnButtonPressed,
+                          base::Unretained(this),
+                          base::Unretained(speed_button_)))),
+          views::Builder<views::Separator>()
+              .SetColor(AshColorProvider::Get()->GetContentLayerColor(
+                  AshColorProvider::ContentLayerType::kSeparatorColor))
+              .SetPreferredHeight(kSeparatorHeight)
+              .SetBorder(views::CreateEmptyBorder(
+                  separator_spacing - kUnifiedTopShortcutSpacing, 0,
+                  separator_spacing, 0)),
+          views::Builder<views::BoxLayoutView>()
+              .SetInsideBorderInsets(gfx::Insets(0, kStopButtonPadding,
+                                                 kStopButtonPadding,
+                                                 kStopButtonPadding))
+              .SetBetweenChildSpacing(kStopButtonPadding)
+              .AddChildren(
+                  views::Builder<FloatingMenuButton>()
+                      .CopyAddressTo(&stop_button_)
+                      .SetID(static_cast<int>(ButtonId::kStop))
+                      .SetVectorIcon(kSelectToSpeakStopIcon)
+                      .SetDrawHighlight(false)
+                      .SetPreferredSize(gfx::Size(kButtonSize, kButtonSize))
+                      .SetTooltipText(l10n_util::GetStringUTF16(
+                          IDS_ASH_SELECT_TO_SPEAK_EXIT))
+                      .SetCallback(base::BindRepeating(
+                          &SelectToSpeakMenuView::OnButtonPressed,
+                          base::Unretained(this),
+                          base::Unretained(stop_button_)))))
       .BuildChildren();
 }
 
diff --git a/ash/system/accessibility/switch_access/switch_access_back_button_view.cc b/ash/system/accessibility/switch_access/switch_access_back_button_view.cc
index 0e14318..f0cfa55a 100644
--- a/ash/system/accessibility/switch_access/switch_access_back_button_view.cc
+++ b/ash/system/accessibility/switch_access/switch_access_back_button_view.cc
@@ -41,17 +41,17 @@
   views::Builder<SwitchAccessBackButtonView>(this)
       .SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kCenter)
       .AddChildren(
-          {views::Builder<FloatingMenuButton>()
-               .CopyAddressTo(&back_button_)
-               .SetVectorIcon(for_menu ? kSwitchAccessCloseIcon
-                                       : kSwitchAccessBackIcon)
-               .SetTooltipText(l10n_util::GetStringUTF16(
-                   IDS_ASH_SWITCH_ACCESS_BACK_BUTTON_DESCRIPTION))
-               .SetPreferredSize(gfx::Size(2 * kRadiusDp, 2 * kRadiusDp))
-               .SetDrawHighlight(true)
-               .SetCallback(base::BindRepeating(
-                   &SwitchAccessBackButtonView::OnButtonPressed,
-                   base::Unretained(this)))})
+          views::Builder<FloatingMenuButton>()
+              .CopyAddressTo(&back_button_)
+              .SetVectorIcon(for_menu ? kSwitchAccessCloseIcon
+                                      : kSwitchAccessBackIcon)
+              .SetTooltipText(l10n_util::GetStringUTF16(
+                  IDS_ASH_SWITCH_ACCESS_BACK_BUTTON_DESCRIPTION))
+              .SetPreferredSize(gfx::Size(2 * kRadiusDp, 2 * kRadiusDp))
+              .SetDrawHighlight(true)
+              .SetCallback(base::BindRepeating(
+                  &SwitchAccessBackButtonView::OnButtonPressed,
+                  base::Unretained(this))))
       .SetSize(gfx::Size(side_length, side_length))
       .BuildChildren();
 }
diff --git a/ash/system/accessibility/switch_access/switch_access_menu_button.cc b/ash/system/accessibility/switch_access/switch_access_menu_button.cc
index 5c8f156d..8a98603 100644
--- a/ash/system/accessibility/switch_access/switch_access_menu_button.cc
+++ b/ash/system/accessibility/switch_access/switch_access_menu_button.cc
@@ -50,17 +50,17 @@
   views::Builder<SwitchAccessMenuButton>(this)
       .SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY)
       .AddChildren(
-          {views::Builder<views::ImageView>()
-               .CopyAddressTo(&image_view_)
-               .SetImage(gfx::CreateVectorIcon(icon, kIconSizeDip, icon_color)),
-           views::Builder<views::Label>()
-               .CopyAddressTo(&label_)
-               .SetText(label_text)
-               .SetTextContext(views::style::CONTEXT_BUTTON)
-               .SetAutoColorReadabilityEnabled(false)
-               .SetEnabledColor(label_color)
-               .SetMultiLine(true)
-               .SetMaximumWidth(kLabelMaxWidthDip)})
+          views::Builder<views::ImageView>()
+              .CopyAddressTo(&image_view_)
+              .SetImage(gfx::CreateVectorIcon(icon, kIconSizeDip, icon_color)),
+          views::Builder<views::Label>()
+              .CopyAddressTo(&label_)
+              .SetText(label_text)
+              .SetTextContext(views::style::CONTEXT_BUTTON)
+              .SetAutoColorReadabilityEnabled(false)
+              .SetEnabledColor(label_color)
+              .SetMultiLine(true)
+              .SetMaximumWidth(kLabelMaxWidthDip))
       .BuildChildren();
 
   std::unique_ptr<views::BoxLayout> layout = std::make_unique<views::BoxLayout>(
diff --git a/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc b/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc
index 064accb..b454674 100644
--- a/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc
+++ b/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc
@@ -231,7 +231,7 @@
 
   views::ToggleButton* toggle =
       (views::ToggleButton*)toggles_map_[internal_mic.id]->children()[1];
-  EXPECT_FALSE(toggle->GetIsOn());
+  EXPECT_TRUE(toggle->GetIsOn());
 }
 
 TEST_F(UnifiedAudioDetailedViewControllerTest,
@@ -239,7 +239,7 @@
   scoped_feature_list_.InitAndEnableFeature(
       features::kEnableInputNoiseCancellationUi);
 
-  audio_pref_handler_->SetNoiseCancellationState(true);
+  audio_pref_handler_->SetNoiseCancellationState(false);
 
   fake_cras_audio_client()->SetAudioNodesAndNotifyObserversForTesting(
       GenerateAudioNodeList({kInternalMic, kMicJack, kFrontMic, kRearMic}));
@@ -258,8 +258,8 @@
   widget->SetContentsView(toggle);
 
   // The toggle loaded the pref correctly.
-  EXPECT_TRUE(toggle->GetIsOn());
-  EXPECT_TRUE(audio_pref_handler_->GetNoiseCancellationState());
+  EXPECT_FALSE(toggle->GetIsOn());
+  EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
 
   ui::MouseEvent press(ui::ET_MOUSE_PRESSED, gfx::PointF(), gfx::PointF(),
                        ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
@@ -268,11 +268,11 @@
   // Flipping the toggle.
   views::test::ButtonTestApi(toggle).NotifyClick(press);
   // The new state of the toggle must be saved to the prefs.
-  EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
+  EXPECT_TRUE(audio_pref_handler_->GetNoiseCancellationState());
 
   // Flipping back and checking the prefs again.
   views::test::ButtonTestApi(toggle).NotifyClick(press);
-  EXPECT_TRUE(audio_pref_handler_->GetNoiseCancellationState());
+  EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
 }
 
 // TODO(1205197): Remove this test once the flag is removed.
diff --git a/ash/system/bluetooth/bluetooth_detailed_view.cc b/ash/system/bluetooth/bluetooth_detailed_view.cc
index 1627ffa..575a9b7 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view.cc
+++ b/ash/system/bluetooth/bluetooth_detailed_view.cc
@@ -23,6 +23,7 @@
 #include "base/bind.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chromeos/ui/vector_icons/vector_icons.h"
+#include "device/bluetooth/chromeos/bluetooth_utils.h"
 #include "services/device/public/cpp/bluetooth/bluetooth_utils.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -135,6 +136,8 @@
                                              LoginStatus login)
     : TrayDetailedView(delegate), login_(login) {
   CreateItems();
+  device::RecordUiSurfaceDisplayed(
+      device::BluetoothUiSurface::kBluetoothQuickSettings);
 }
 
 BluetoothDetailedView::~BluetoothDetailedView() = default;
diff --git a/ash/system/holding_space/holding_space_item_screen_capture_view.cc b/ash/system/holding_space/holding_space_item_screen_capture_view.cc
index 3d8e6f3..9d44933 100644
--- a/ash/system/holding_space/holding_space_item_screen_capture_view.cc
+++ b/ash/system/holding_space/holding_space_item_screen_capture_view.cc
@@ -61,7 +61,7 @@
                                                   kHoldingSpaceIconSize))));
   }
 
-  builder
+  std::move(builder)
       .AddChild(
           views::Builder<views::FlexLayoutView>()
               .SetOrientation(views::LayoutOrientation::kHorizontal)
diff --git a/ash/system/holding_space/holding_space_tray.cc b/ash/system/holding_space/holding_space_tray.cc
index ace2d3d..f87226fe 100644
--- a/ash/system/holding_space/holding_space_tray.cc
+++ b/ash/system/holding_space/holding_space_tray.cc
@@ -99,7 +99,7 @@
 std::vector<base::FilePath> ExtractFilePathsFromFileSystemSources(
     const ui::OSExchangeData& data) {
   base::Pickle pickle;
-  if (!data.GetPickledData(ui::ClipboardFormatType::GetWebCustomDataType(),
+  if (!data.GetPickledData(ui::ClipboardFormatType::WebCustomDataType(),
                            &pickle)) {
     return {};
   }
@@ -394,7 +394,7 @@
   // Support custom web data so that file system sources can be retrieved from
   // pickled data. That is the storage location at which the Files app stores
   // both file paths *and* directory paths.
-  format_types->insert(ui::ClipboardFormatType::GetWebCustomDataType());
+  format_types->insert(ui::ClipboardFormatType::WebCustomDataType());
   return true;
 }
 
diff --git a/ash/test/view_drawn_waiter.cc b/ash/test/view_drawn_waiter.cc
new file mode 100644
index 0000000..81df3af6
--- /dev/null
+++ b/ash/test/view_drawn_waiter.cc
@@ -0,0 +1,57 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/test/view_drawn_waiter.h"
+
+#include "base/check.h"
+#include "base/notreached.h"
+#include "base/run_loop.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace {
+
+bool IsDrawn(views::View* view) {
+  return view->IsDrawn() && !view->size().IsEmpty();
+}
+
+}  // namespace
+
+ViewDrawnWaiter::ViewDrawnWaiter() = default;
+
+ViewDrawnWaiter::~ViewDrawnWaiter() = default;
+
+void ViewDrawnWaiter::Wait(views::View* view) {
+  if (IsDrawn(view))
+    return;
+
+  DCHECK(!wait_loop_);
+  DCHECK(!view_observer_.IsObserving());
+
+  view_observer_.Observe(view);
+
+  wait_loop_ = std::make_unique<base::RunLoop>();
+  wait_loop_->Run();
+  wait_loop_.reset();
+
+  view_observer_.Reset();
+}
+
+void ViewDrawnWaiter::OnViewVisibilityChanged(views::View* view,
+                                              views::View* starting_view) {
+  if (IsDrawn(view))
+    wait_loop_->Quit();
+}
+
+void ViewDrawnWaiter::OnViewBoundsChanged(views::View* view) {
+  if (IsDrawn(view))
+    wait_loop_->Quit();
+}
+
+void ViewDrawnWaiter::OnViewIsDeleting(views::View* view) {
+  NOTREACHED() << "View deleted while waiting for it to be visible";
+}
+
+}  // namespace ash
diff --git a/ash/test/view_drawn_waiter.h b/ash/test/view_drawn_waiter.h
new file mode 100644
index 0000000..0f685b4
--- /dev/null
+++ b/ash/test/view_drawn_waiter.h
@@ -0,0 +1,48 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_TEST_VIEW_DRAWN_WAITER_H_
+#define ASH_TEST_VIEW_DRAWN_WAITER_H_
+
+#include <memory>
+
+#include "ash/ash_export.h"
+#include "base/scoped_observation.h"
+#include "ui/views/view.h"
+#include "ui/views/view_observer.h"
+
+namespace base {
+class RunLoop;
+}
+
+namespace ash {
+
+// Helper that allows waiting for a view to be drawn. See `Wait()` below. An
+// instance can be used more than once (i.e. to wait on a different view).
+class ViewDrawnWaiter : public views::ViewObserver {
+ public:
+  ViewDrawnWaiter();
+  ViewDrawnWaiter(const ViewDrawnWaiter&) = delete;
+  ViewDrawnWaiter& operator=(const ViewDrawnWaiter&) = delete;
+  ~ViewDrawnWaiter() override;
+
+  // Waits for `view` to be drawn (implying visible) and have non-zero size
+  // (implying layout is complete).
+  void Wait(views::View* view);
+
+ private:
+  // views::ViewObserver:
+  void OnViewVisibilityChanged(views::View* view,
+                               views::View* starting_view) override;
+  void OnViewBoundsChanged(views::View* view) override;
+  void OnViewIsDeleting(views::View* view) override;
+
+  std::unique_ptr<base::RunLoop> wait_loop_;
+  base::ScopedObservation<views::View, views::ViewObserver> view_observer_{
+      this};
+};
+
+}  // namespace ash
+
+#endif  // ASH_TEST_VIEW_DRAWN_WAITER_H_
diff --git a/ash/webui/BUILD.gn b/ash/webui/BUILD.gn
index 0ddbc869..5bb0638 100644
--- a/ash/webui/BUILD.gn
+++ b/ash/webui/BUILD.gn
@@ -17,6 +17,7 @@
   ]
 
   deps = [
+    "//ash/webui/common/backend:unit_tests",
     "//ash/webui/diagnostics_ui/backend:unit_tests",
     "//ash/webui/scanning:unit_tests",
     "//ash/webui/shimless_rma/backend:unit_tests",
diff --git a/ash/webui/common/backend/BUILD.gn b/ash/webui/common/backend/BUILD.gn
new file mode 100644
index 0000000..4945555c1
--- /dev/null
+++ b/ash/webui/common/backend/BUILD.gn
@@ -0,0 +1,36 @@
+# Copyright 2021 The Chromium Authors.All rights reserved.
+# Use of this source code is governed by a BSD - style license that can be
+# found in the LICENSE file.
+
+assert(is_chromeos, "Non-ChromeOS builds cannot depend on //chromeos")
+
+static_library("backend") {
+  sources = [
+    "plural_string_handler.cc",
+    "plural_string_handler.h",
+  ]
+
+  deps = [
+    "//base",
+    "//chromeos/strings/",
+    "//content/public/browser",
+    "//ui/base",
+    "//ui/webui",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+
+  sources = [ "plural_string_handler_unittest.cc" ]
+
+  deps = [
+    ":backend",
+    "//base",
+    "//base/test:test_support",
+    "//chromeos/strings/",
+    "//content/test:test_support",
+    "//testing/gtest",
+    "//ui/webui",
+  ]
+}
diff --git a/ash/webui/common/backend/plural_string_handler.cc b/ash/webui/common/backend/plural_string_handler.cc
new file mode 100644
index 0000000..9124249
--- /dev/null
+++ b/ash/webui/common/backend/plural_string_handler.cc
@@ -0,0 +1,50 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/webui/common/backend/plural_string_handler.h"
+
+#include "base/check.h"
+#include "base/containers/contains.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "chromeos/strings/grit/chromeos_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace ash {
+
+PluralStringHandler::PluralStringHandler() = default;
+PluralStringHandler::~PluralStringHandler() = default;
+
+void PluralStringHandler::RegisterMessages() {
+  web_ui()->RegisterMessageCallback(
+      "getPluralString",
+      base::BindRepeating(&PluralStringHandler::HandleGetPluralString,
+                          base::Unretained(this)));
+}
+
+void PluralStringHandler::AddStringToPluralMap(const std::string& name,
+                                               int string_id) {
+  DCHECK(!base::Contains(string_id_map_, name));
+  string_id_map_[name] = string_id;
+}
+
+void PluralStringHandler::HandleGetPluralString(const base::ListValue* args) {
+  AllowJavascript();
+  CHECK_EQ(3U, args->GetSize());
+  const std::string callback = args->GetList()[0].GetString();
+  const std::string name = args->GetList()[1].GetString();
+  const int count = args->GetList()[2].GetInt();
+  DCHECK(base::Contains(string_id_map_, name));
+  const std::u16string localized_string =
+      l10n_util::GetPluralStringFUTF16(string_id_map_.at(name), count);
+  ResolveJavascriptCallback(base::Value(callback),
+                            base::Value(localized_string));
+}
+
+void PluralStringHandler::SetWebUIForTest(content::WebUI* web_ui) {
+  DCHECK(web_ui);
+  set_web_ui(web_ui);
+}
+
+}  // namespace ash
diff --git a/ash/webui/common/backend/plural_string_handler.h b/ash/webui/common/backend/plural_string_handler.h
new file mode 100644
index 0000000..3a5813d
--- /dev/null
+++ b/ash/webui/common/backend/plural_string_handler.h
@@ -0,0 +1,45 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WEBUI_COMMON_BACKEND_PLURAL_STRING_HANDLER_H_
+#define ASH_WEBUI_COMMON_BACKEND_PLURAL_STRING_HANDLER_H_
+
+#include <map>
+#include <string>
+
+#include "content/public/browser/web_ui_message_handler.h"
+
+namespace base {
+class ListValue;
+}  // namespace base
+
+namespace ash {
+
+class PluralStringHandler : public content::WebUIMessageHandler {
+ public:
+  PluralStringHandler();
+
+  PluralStringHandler(const PluralStringHandler&) = delete;
+  PluralStringHandler& operator=(const PluralStringHandler&) = delete;
+
+  ~PluralStringHandler() override;
+
+  // WebUIMessageHandler:
+  void RegisterMessages() override;
+
+  // Adds to map of string IDs for pluralization.
+  void AddStringToPluralMap(const std::string& name, int id);
+
+  void SetWebUIForTest(content::WebUI* web_ui);
+
+ private:
+  // Returns a localized, pluralized string.
+  void HandleGetPluralString(const base::ListValue* args);
+
+  std::map<std::string, int> string_id_map_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_WEBUI_COMMON_BACKEND_PLURAL_STRING_HANDLER_H_
diff --git a/ash/webui/common/backend/plural_string_handler_unittest.cc b/ash/webui/common/backend/plural_string_handler_unittest.cc
new file mode 100644
index 0000000..c660aca
--- /dev/null
+++ b/ash/webui/common/backend/plural_string_handler_unittest.cc
@@ -0,0 +1,86 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/webui/common/backend/plural_string_handler.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "base/values.h"
+#include "chromeos/strings/grit/chromeos_strings.h"
+#include "content/public/test/test_web_ui.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+namespace {
+constexpr char kHandlerFunctionName[] = "handlerFunctionName";
+
+}  // namespace
+
+class PluralStringHandlerTest : public testing::Test {
+ public:
+  PluralStringHandlerTest() : task_environment_(), web_ui_() {
+    plural_string_handler_ = std::make_unique<PluralStringHandler>();
+    plural_string_handler_->SetWebUIForTest(&web_ui_);
+    plural_string_handler_->RegisterMessages();
+    // Add edit button label to plural map for testing purposes.
+    plural_string_handler_->AddStringToPluralMap(
+        "editButtonLabel", IDS_SCANNING_APP_EDIT_BUTTON_LABEL);
+  }
+
+  const content::TestWebUI::CallData& CallDataAtIndex(size_t index) {
+    return *web_ui_.call_data()[index];
+  }
+
+  ~PluralStringHandlerTest() override = default;
+
+ protected:
+  base::test::TaskEnvironment task_environment_;
+  content::TestWebUI web_ui_;
+  std::unique_ptr<PluralStringHandler> plural_string_handler_;
+};
+
+TEST_F(PluralStringHandlerTest, PluralString) {
+  // base::RunLoop run_loop;
+  const int call_data_count_before_call = web_ui_.call_data().size();
+  base::ListValue args;
+  args.Append(kHandlerFunctionName);
+  args.Append("editButtonLabel");
+  args.Append(/*count=*/2);
+  web_ui_.HandleReceivedMessage("getPluralString", &args);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(call_data_count_before_call + 1u, web_ui_.call_data().size());
+  const content::TestWebUI::CallData& call_data =
+      CallDataAtIndex(call_data_count_before_call);
+  EXPECT_EQ("cr.webUIResponse", call_data.function_name());
+  EXPECT_EQ("handlerFunctionName", call_data.arg1()->GetString());
+  EXPECT_TRUE(/*success=*/call_data.arg2()->GetBool());
+  EXPECT_EQ("Edit files", call_data.arg3()->GetString());
+}
+
+TEST_F(PluralStringHandlerTest, SingularString) {
+  const int call_data_count_before_call = web_ui_.call_data().size();
+  base::ListValue args;
+  args.Append(kHandlerFunctionName);
+  args.Append("editButtonLabel");
+  args.Append(/*count=*/1);
+  web_ui_.HandleReceivedMessage("getPluralString", &args);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(call_data_count_before_call + 1u, web_ui_.call_data().size());
+  const content::TestWebUI::CallData& call_data =
+      CallDataAtIndex(call_data_count_before_call);
+  EXPECT_EQ("cr.webUIResponse", call_data.function_name());
+  EXPECT_EQ("handlerFunctionName", call_data.arg1()->GetString());
+  EXPECT_TRUE(/*success=*/call_data.arg2()->GetBool());
+  EXPECT_EQ("Edit file", call_data.arg3()->GetString());
+}
+
+}  // namespace ash
diff --git a/ash/webui/diagnostics_ui/backend/session_log_handler.cc b/ash/webui/diagnostics_ui/backend/session_log_handler.cc
index c03f83ef..8ae74dd 100644
--- a/ash/webui/diagnostics_ui/backend/session_log_handler.cc
+++ b/ash/webui/diagnostics_ui/backend/session_log_handler.cc
@@ -9,7 +9,7 @@
 #include "ash/webui/diagnostics_ui/backend/telemetry_log.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
-#include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/thread_pool.h"
 #include "base/values.h"
@@ -117,10 +117,11 @@
   // Fetch TelemetryLog
   const std::string telemetry_log_contents = telemetry_log_->GetContents();
 
-  const std::string combined_contents =
-      base::StrCat({kTelemetryLogSectionHeader, telemetry_log_contents,
-                    kRoutineLogSectionHeader, routine_log_contents});
-  return base::WriteFile(file_path, combined_contents);
+  const std::vector<std::string> pieces = {
+      kTelemetryLogSectionHeader, telemetry_log_contents,
+      kRoutineLogSectionHeader, routine_log_contents};
+
+  return base::WriteFile(file_path, base::JoinString(pieces, "\n"));
 }
 
 void SessionLogHandler::HandleSaveSessionLogRequest(
diff --git a/ash/webui/diagnostics_ui/diagnostics_ui.cc b/ash/webui/diagnostics_ui/diagnostics_ui.cc
index ab002c1..58dd3bb 100644
--- a/ash/webui/diagnostics_ui/diagnostics_ui.cc
+++ b/ash/webui/diagnostics_ui/diagnostics_ui.cc
@@ -110,6 +110,12 @@
       {"httpFirewallRoutineText", IDS_NETWORK_DIAGNOSTICS_HTTP_FIREWALL},
       {"httpsFirewallRoutineText", IDS_NETWORK_DIAGNOSTICS_HTTPS_FIREWALL},
       {"httpsLatencyRoutineText", IDS_NETWORK_DIAGNOSTICS_HTTPS_LATENCY},
+      {"ipConfigInfoDrawerGateway",
+       IDS_NETWORK_DIAGNOSTICS_IP_CONFIG_INFO_DRAWER_GATEWAY},
+      {"ipConfigInfoDrawerMacAddress",
+       IDS_NETWORK_DIAGNOSTICS_IP_CONFIG_INFO_DRAWER_MAC_ADDRESS},
+      {"ipConfigInfoDrawerSubnetMask",
+       IDS_NETWORK_DIAGNOSTICS_IP_CONFIG_INFO_DRAWER_SUBNET_MASK},
       {"ipConfigInfoDrawerTitle",
        IDS_NETWORK_DIAGNOSTICS_IP_CONFIG_INFO_DRAWER_TITLE},
       {"lanConnectivityRoutineText", IDS_NETWORK_DIAGNOSTICS_LAN_CONNECTIVITY},
diff --git a/ash/webui/diagnostics_ui/resources/BUILD.gn b/ash/webui/diagnostics_ui/resources/BUILD.gn
index 0c964c6..07b0e37 100644
--- a/ash/webui/diagnostics_ui/resources/BUILD.gn
+++ b/ash/webui/diagnostics_ui/resources/BUILD.gn
@@ -70,6 +70,7 @@
 
 js_library("connectivity_card") {
   deps = [
+    ":ip_config_info_drawer",
     ":network_info",
     ":routine_section",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -174,7 +175,9 @@
 
 js_library("ip_config_info_drawer") {
   deps = [
+    ":data_point",
     ":diagnostics_types",
+    ":diagnostics_utils",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_button:cr_button.m",
     "//ui/webui/resources/js:i18n_behavior.m",
diff --git a/ash/webui/diagnostics_ui/resources/connectivity_card.html b/ash/webui/diagnostics_ui/resources/connectivity_card.html
index 6766e18..be95907 100644
--- a/ash/webui/diagnostics_ui/resources/connectivity_card.html
+++ b/ash/webui/diagnostics_ui/resources/connectivity_card.html
@@ -24,12 +24,6 @@
   hide-routine-status="true"
   opened="true">
 </routine-section>
-<cr-expand-button slot="routines" expanded="{{expanded_}}">
-  <span aria-hidden="true">IP Configuration</span>
-</cr-expand-button>
-<template is="dom-if" if="[[expanded_]]">
-  <network-info slot="routines" show-ip-config-properties="true"
-    network="[[network]]">
-  </network-info>
-</template>
+<ip-config-info-drawer id="ipConfigInfoDrawer" network="[[network]]"
+  slot="routines"></ip-config-info-drawer>
 </diagnostics-card>
diff --git a/ash/webui/diagnostics_ui/resources/connectivity_card.js b/ash/webui/diagnostics_ui/resources/connectivity_card.js
index 6ae1f8e..62edb7a 100644
--- a/ash/webui/diagnostics_ui/resources/connectivity_card.js
+++ b/ash/webui/diagnostics_ui/resources/connectivity_card.js
@@ -5,9 +5,9 @@
 import './diagnostics_card.js';
 import './diagnostics_fonts_css.js';
 import './diagnostics_shared_css.js';
+import './ip_config_info_drawer.js';
 import './network_info.js';
 import './routine_section.js';
-import 'chrome://resources/cr_elements/cr_expand_button/cr_expand_button.m.js';
 
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -89,12 +89,6 @@
       type: String,
       value: '',
     },
-
-    /** @private {boolean} */
-    expanded_: {
-      type: Boolean,
-      value: false,
-    },
   },
 
   observers: ['observeNetwork_(activeGuid)'],
diff --git a/ash/webui/diagnostics_ui/resources/data_point.html b/ash/webui/diagnostics_ui/resources/data_point.html
index ffe6728..3ba4bbab 100644
--- a/ash/webui/diagnostics_ui/resources/data_point.html
+++ b/ash/webui/diagnostics_ui/resources/data_point.html
@@ -1,27 +1,35 @@
 <style include="diagnostics-shared diagnostics-fonts">
-  :host([orientation=vertical]) .data-point {
+  :host([orientation=horizontal]) {
+    --diagnostics-data-point-subtitle-color: var(--cros-text-color-primary);
+    --diagnostics-data-point-subtitle-font-size: 12px;
+    --diagnostics-data-point-title-color: var(--cros-text-color-secondary);
+    --diagnostics-data-point-title-font-size: 12px;
+    line-height: 18px;
+  }
+  .data-point {
     display: flex;
+  }
+
+  :host([orientation=vertical]) .data-point {
     flex-direction: column;
     height: 40px;
     justify-content: space-between;
     margin: 12px 0;
   }
 
-  :host([orientation=horizontal]) .data-point {
-    display: flex;
-    justify-content: space-between;
-    margin: 12px 0;
-    width: 108px;
-  }
-
   .header {
     @apply --diagnostics-data-point-title-font;
   }
 
+  :host([orientation=horizontal]) .header {
+    margin-right: 16px;
+    width: 96px;
+  }
+
   #infoIcon {
-    bottom: 1px;
     --iron-icon-height: 20px;
     --iron-icon-width: 20px;
+    bottom: 1px;
     fill: var(--google-grey-700);
     left: 4px;
   }
@@ -30,6 +38,10 @@
     @apply --diagnostics-data-point-subtitle-font;
   }
 
+  :host([orientation=horizontal]) .value {
+    width: 168px;
+  }
+
   .text-red {
     color: var(--google-red-600);
   }
diff --git a/ash/webui/diagnostics_ui/resources/diagnostics_fonts_css.html b/ash/webui/diagnostics_ui/resources/diagnostics_fonts_css.html
index 0dfab0f..f7c7f8e6 100644
--- a/ash/webui/diagnostics_ui/resources/diagnostics_fonts_css.html
+++ b/ash/webui/diagnostics_ui/resources/diagnostics_fonts_css.html
@@ -63,7 +63,7 @@
       };
       --diagnostics-data-point-subtitle-font: {
           color: var(--diagnostics-data-point-subtitle-color);
-          font-family: --diagnostics-data-point-font-family
+          font-family: var(--diagnostics-data-point-font-family);
           font-size: var(--diagnostics-data-point-subtitle-font-size);
           font-weight: var(--diagnostics-regular-font-weight);
       };
diff --git a/ash/webui/diagnostics_ui/resources/diagnostics_shared_css.html b/ash/webui/diagnostics_ui/resources/diagnostics_shared_css.html
index 90a50c7..99ee318 100644
--- a/ash/webui/diagnostics_ui/resources/diagnostics_shared_css.html
+++ b/ash/webui/diagnostics_ui/resources/diagnostics_shared_css.html
@@ -26,6 +26,19 @@
       margin-left: 20px;
     }
 
+    data-point[orientation=horizontal] {
+      margin: 9px 0;
+      width: 300px;
+    }
+
+    data-point[orientation=horizontal]:first-of-type {
+      margin-top: 8px;
+    }
+
+    data-point[orientation=horizontal]:last-of-type {
+      margin-bottom: 8px;
+    }
+
     paper-tooltip {
       --paper-tooltip-min-width: auto;
       line-height: 18px;
@@ -65,8 +78,8 @@
     .divider-horizontal {
       align-self: center;
       border-left: 1px solid var(--google-grey-200);
-      height: 94px;
-      margin: 10px 0;;
+      height: var(--divider-horizontal-height, 94px);
+      margin: 10px 20px;
     }
 
     .grey-container {
@@ -76,9 +89,13 @@
     }
 
     .horizontal-data-point-container {
-      display: flex;
-      justify-content: space-between;
-      margin-top: 12px;
+      display: grid;
+      margin: 20px 0;
+      grid-template-columns: 300px 40px 300px;
+    }
+
+    .horizontal-data-point-container .data-point-container {
+      width: fit-content;
     }
 
     .link-text {
diff --git a/ash/webui/diagnostics_ui/resources/ip_config_info_drawer.html b/ash/webui/diagnostics_ui/resources/ip_config_info_drawer.html
index 2da16b0..d473efa 100644
--- a/ash/webui/diagnostics_ui/resources/ip_config_info_drawer.html
+++ b/ash/webui/diagnostics_ui/resources/ip_config_info_drawer.html
@@ -4,5 +4,23 @@
   <span aria-hidden="true">[[i18n('ipConfigInfoDrawerTitle')]]</span>
 </cr-expand-button>
 <template is="dom-if" if="[[expanded_]]">
-  <div id="ipConfigInfoElement"></div>
+  <div id="ipConfigInfoElement" class="horizontal-data-point-container">
+    <div class="data-point-container">
+      <data-point id="macAddress"
+        header="[[i18n('ipConfigInfoDrawerMacAddress')]]"
+        value="[[macAddress_]]" orientation="horizontal"></data-point>
+      <data-point id="nameServers"
+        header="Name servers"
+        value="[[nameServers_]]" orientation="horizontal"></data-point>
+    </div>
+    <div class="divider-horizontal"></div>
+    <div class="data-point-container">
+      <data-point id="gateway"
+        header="[[i18n('ipConfigInfoDrawerGateway')]]"
+        value="[[gateway_]]" orientation="horizontal"></data-point>
+      <data-point id="subnetMask"
+        header="[[i18n('ipConfigInfoDrawerSubnetMask')]]"
+        value="[[subnetMask_]]" orientation="horizontal"></data-point>
+    </div>
+  </div>
 </template>
diff --git a/ash/webui/diagnostics_ui/resources/ip_config_info_drawer.js b/ash/webui/diagnostics_ui/resources/ip_config_info_drawer.js
index e211763..23b6fb2 100644
--- a/ash/webui/diagnostics_ui/resources/ip_config_info_drawer.js
+++ b/ash/webui/diagnostics_ui/resources/ip_config_info_drawer.js
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './data_point.js';
 import './diagnostics_fonts_css.js';
 import './diagnostics_shared_css.js';
 
@@ -9,6 +10,9 @@
 import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {Network} from './diagnostics_types.js';
+import {getSubnetMaskFromRoutingPrefix} from './diagnostics_utils.js';
+
 /**
  * @fileoverview
  * 'ip-config-info-drawer' displays standard IP related configuration data in a
@@ -30,5 +34,89 @@
       type: Boolean,
       value: false,
     },
+
+    /**
+     * @protected
+     * @type {string}
+     */
+    gateway_: {
+      type: String,
+      computed: 'computeGateway_(network.ipConfig.gateway)',
+    },
+
+    /**
+     * @protected
+     * @type {string}
+     */
+    macAddress_: {
+      type: String,
+      computed: 'computeMacAddress_(network.macAddress)',
+    },
+
+    /**
+     * @protected
+     * @type {string}
+     */
+    nameServers_: {
+      type: String,
+      computed: 'computeNameServers_(network.ipConfig.nameServers)',
+    },
+
+    /** @type {!Network} */
+    network: {
+      type: Object,
+    },
+
+    /**
+     * @protected
+     * @type {string}
+     */
+    subnetMask_: {
+      type: String,
+      computed: 'computeSubnetMask_(network.ipConfig.routingPrefix)',
+    },
+  },
+
+  /**
+   * @protected
+   * @return {string}
+   */
+  computeGateway_() {
+    if (this.network.ipConfig && this.network.ipConfig.gateway) {
+      return this.network.ipConfig.gateway;
+    }
+    return '';
+  },
+
+  /**
+   * @protected
+   * @return {string}
+   */
+  computeMacAddress_() {
+    return this.network.macAddress || '';
+  },
+
+  /**
+   * @protected
+   * @return {string}
+   */
+  computeNameServers_() {
+    // Both ipConfig and nameServers could be null.
+    if (this.network.ipConfig && this.network.ipConfig.nameServers) {
+      return this.network.ipConfig.nameServers.join(', ');
+    }
+    return '';
+  },
+
+  /**
+   * @protected
+   * @return {string}
+   */
+  computeSubnetMask_() {
+    if (this.network.ipConfig && this.network.ipConfig.routingPrefix) {
+      return getSubnetMaskFromRoutingPrefix(
+          this.network.ipConfig.routingPrefix);
+    }
+    return '';
   },
 });
diff --git a/ash/webui/diagnostics_ui/resources/network_card.html b/ash/webui/diagnostics_ui/resources/network_card.html
index 3a5b25f..78b72e32 100644
--- a/ash/webui/diagnostics_ui/resources/network_card.html
+++ b/ash/webui/diagnostics_ui/resources/network_card.html
@@ -35,6 +35,6 @@
   </div>
   <network-info slot="body" guid="[[guid]]" network="[[network]]">
   </network-info>
-  <ip-config-info-drawer id="ipConfigInfoDrawer" slot="routines">
-  </ip-config-info-drawer>
+  <ip-config-info-drawer id="ipConfigInfoDrawer" network="[[network]]"
+    slot="routines"></ip-config-info-drawer>
 </diagnostics-card>
diff --git a/ash/webui/diagnostics_ui/resources/network_info.html b/ash/webui/diagnostics_ui/resources/network_info.html
index 66ec12a2..9dd27fd 100644
--- a/ash/webui/diagnostics_ui/resources/network_info.html
+++ b/ash/webui/diagnostics_ui/resources/network_info.html
@@ -4,8 +4,7 @@
 <div id="infoElementContainer">
   <template is="dom-if"
       if="[[isWifiNetwork_(network.type)]]">
-    <wifi-info show-ip-config-properties="[[showIpConfigProperties]]"
-        id="wifiInfo" network="[[network]]">
+    <wifi-info id="wifiInfo" network="[[network]]">
     </wifi-info>
   </template>
   <template is="dom-if"
@@ -18,4 +17,4 @@
     <cellular-info id="cellularInfo" network="[[network]]">
     </cellular-info>
   </template>
-</div>
\ No newline at end of file
+</div>
diff --git a/ash/webui/diagnostics_ui/resources/network_info.js b/ash/webui/diagnostics_ui/resources/network_info.js
index 5b2426e..dde8537 100644
--- a/ash/webui/diagnostics_ui/resources/network_info.js
+++ b/ash/webui/diagnostics_ui/resources/network_info.js
@@ -27,13 +27,6 @@
     network: {
       type: Object,
     },
-
-    /** @type {boolean} */
-    showIpConfigProperties: {
-      type: Boolean,
-      value: false,
-      reflectToAttribute: true,
-    },
   },
 
   /**
diff --git a/ash/webui/diagnostics_ui/resources/network_list.html b/ash/webui/diagnostics_ui/resources/network_list.html
index 3cc9db3..3904f77 100644
--- a/ash/webui/diagnostics_ui/resources/network_list.html
+++ b/ash/webui/diagnostics_ui/resources/network_list.html
@@ -12,6 +12,10 @@
     top: 1px;
   }
 
+  #networkListContainer {
+    overflow: hidden;
+  }
+
   #settingsLink {
     @apply --diagnostics-settings-link-font;
   }
diff --git a/ash/webui/diagnostics_ui/resources/wifi_info.html b/ash/webui/diagnostics_ui/resources/wifi_info.html
index a51f359..e242e70 100644
--- a/ash/webui/diagnostics_ui/resources/wifi_info.html
+++ b/ash/webui/diagnostics_ui/resources/wifi_info.html
@@ -1,8 +1,7 @@
 <style include="diagnostics-shared diagnostics-fonts">
 </style>
 <div id="wifiInfoContainer">
-  <div hidden$="[[showIpConfigProperties]]"
-      class="horizontal-data-point-container">
+  <div class="horizontal-data-point-container">
     <div class="data-point-container">
       <data-point id="name" header="Network Name"
         value="[[network.name]]"
@@ -29,29 +28,4 @@
       </data-point>
     </div>
   </div>
-  <!-- TODO(ashleydp): Move IP configuration data into distinct element. -->
-  <div hidden$="[[!showIpConfigProperties]]"
-      class="horizontal-data-point-container">
-    <div class="data-point-container">
-      <data-point id="macAddress" header="MAC Address"
-        value="[[network.macAddress]]"
-        orientation="horizontal">
-      </data-point>
-      <data-point id="gateway" header="Gateway"
-        value="[[network.ipConfig.gateway]]"
-        orientation="horizontal">
-      </data-point>
-    </div>
-    <div class="divider-horizontal"></div>
-    <div class="data-point-container">
-      <data-point id="nameServers" header="Name Servers"
-        value="[[network.ipConfig.gateway]]"
-        orientation="horizontal">
-      </data-point>
-      <data-point id="subnetMask" header="Subnet Mask"
-        value="[[getSubnetMask_(network.ipConfig.routingPrefix)]]"
-        orientation="horizontal">
-      </data-point>
-    </div>
-  </div>
 </div>
diff --git a/ash/webui/diagnostics_ui/resources/wifi_info.js b/ash/webui/diagnostics_ui/resources/wifi_info.js
index 7899c19..bc29b1b 100644
--- a/ash/webui/diagnostics_ui/resources/wifi_info.js
+++ b/ash/webui/diagnostics_ui/resources/wifi_info.js
@@ -26,22 +26,6 @@
     network: {
       type: Object,
     },
-
-    /** @type {boolean} */
-    showIpConfigProperties: {
-      type: Boolean,
-      value: false,
-    },
-  },
-
-  /**
-   * Returns a concatenated list of strings.
-   * @protected
-   * @param {!Array<string>} nameServers
-   * @return {string}
-   */
-  joinNameServers_(nameServers) {
-    return nameServers ? nameServers.join(', ') : '';
   },
 
   /**
@@ -57,13 +41,4 @@
     const ghz = (frequency / 1000).toFixed(3);
     return `${channel || '?'} (${ghz} GHz)`;
   },
-
-  /**
-   * @protected
-   * @param {number} prefix
-   * @return {string}
-   */
-  getSubnetMask_(prefix) {
-    return getSubnetMaskFromRoutingPrefix(prefix);
-  },
 });
diff --git a/ash/webui/scanning/resources/multi_page_checkbox.html b/ash/webui/scanning/resources/multi_page_checkbox.html
index 34e5c7c..1eb0ecf 100644
--- a/ash/webui/scanning/resources/multi_page_checkbox.html
+++ b/ash/webui/scanning/resources/multi_page_checkbox.html
@@ -27,7 +27,8 @@
   <span slot="label"></span>
   <div slot="settings">
     <div id="checkboxDiv" on-click="onCheckboxClick_">
-      <cr-checkbox class="no-label" checked="{{multiPageScanChecked}}">
+      <cr-checkbox class="no-label" checked="{{multiPageScanChecked}}"
+          disabled="[[disabled]]">
       </cr-checkbox>
       <span id="checkboxText">[[i18n('multiPageCheckboxText')]]</span>
     </div>
diff --git a/ash/webui/scanning/resources/multi_page_checkbox.js b/ash/webui/scanning/resources/multi_page_checkbox.js
index cf9ba03..52e7c0c 100644
--- a/ash/webui/scanning/resources/multi_page_checkbox.js
+++ b/ash/webui/scanning/resources/multi_page_checkbox.js
@@ -25,6 +25,9 @@
       type: Boolean,
       notify: true,
     },
+
+    /** @type {boolean} */
+    disabled: Boolean,
   },
 
   /** @private */
diff --git a/ash/webui/scanning/resources/scan_preview.html b/ash/webui/scanning/resources/scan_preview.html
index b83e6c1..c6c49a43 100644
--- a/ash/webui/scanning/resources/scan_preview.html
+++ b/ash/webui/scanning/resources/scan_preview.html
@@ -95,19 +95,15 @@
     --paper-progress-container-color: var(--google-grey-200);
   }
 
-  #scannedImages {
-    position: relative;
-  }
-
   action-toolbar {
-    display: none;
-    left: 270px;
+    left: var(--action-toolbar-left);
     position: fixed;
-    top: 425px;
+    top: var(--action-toolbar-top);
+    visibility: hidden;
   }
 
   .preview:hover action-toolbar {
-    display: block;
+    visibility: visible;
   }
 </style>
 <div id="previewDiv" class="preview" aria-label="[[previewAriaLabel_]]"
@@ -137,7 +133,7 @@
         num-total-pages="[[objectUrls.length]]"
         current-page-in-view="[[currentPageInView_]]"></action-toolbar>
     <template is="dom-repeat" items="[[objectUrls]]" as="url">
-      <img class="preview-item scanned-image" src="[[url]]">
+      <img class="preview-item scanned-image" src="[[url]]" on-load="onScannedImagesLoaded_">
     </template>
   </div>
 </div>
diff --git a/ash/webui/scanning/resources/scan_preview.js b/ash/webui/scanning/resources/scan_preview.js
index 7542679..a8b1147 100644
--- a/ash/webui/scanning/resources/scan_preview.js
+++ b/ash/webui/scanning/resources/scan_preview.js
@@ -9,6 +9,7 @@
 
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {afterNextRender, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {AppState} from './scanning_app_types.js';
@@ -109,6 +110,25 @@
       type: Number,
       value: -1,
     },
+
+    /**
+     * Set to true once the first scanned image from a scan is loaded. This is
+     * needed to prevent checking the dimensions of every scanned image. The
+     * assumption is that all scanned images share the same dimensions.
+     * @private {boolean}
+     */
+    scannedImagesLoaded_: {
+      type: Boolean,
+      value: false,
+    },
+
+    /** @private {boolean} */
+    scanAppMultiPageScanEnabled_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.getBoolean('scanAppMultiPageScanEnabled');
+      }
+    },
   },
 
   observers: [
@@ -128,6 +148,16 @@
   ready() {
     this.style.setProperty(
         '--scanned-image-margin-bottom', SCANNED_IMG_MARGIN_BOTTOM_PX + 'px');
+    if (this.scanAppMultiPageScanEnabled_) {
+      window.addEventListener('resize', () => this.setActionToolbarPosition_());
+    }
+  },
+
+  /** @override */
+  detached() {
+    if (this.scanAppMultiPageScanEnabled_) {
+      window.removeEventListener('resize', this.setActionToolbarPosition_);
+    }
   },
 
   /** @private */
@@ -262,4 +292,45 @@
           'focused-scanned-image');
     });
   },
+
+  /**
+   * Once the scanned images load, set the action toolbar position.
+   * @private
+   */
+  onScannedImagesLoaded_() {
+    if (!this.scanAppMultiPageScanEnabled_) {
+      return;
+    }
+
+    // If the position was already set after the first scanned image loaded,
+    // there's no need to position it again.
+    if (this.scannedImagesLoaded_) {
+      return;
+    }
+
+    this.scannedImagesLoaded_ = true;
+    this.setActionToolbarPosition_();
+  },
+
+  /**
+   * Set the position of the action toolbar based on the size of the scanned
+   * images and the current size of the app window.
+   * @private
+   */
+  setActionToolbarPosition_() {
+    assert(this.scanAppMultiPageScanEnabled_);
+
+    const scannedImage = this.$$('.scanned-image');
+    if (!scannedImage) {
+      return;
+    }
+
+    const scannedImageRect = scannedImage.getBoundingClientRect();
+    const topPosition = scannedImageRect.height * .85;
+    this.style.setProperty('--action-toolbar-top', topPosition + 'px');
+
+    const leftPosition = scannedImageRect.x + (scannedImageRect.width / 2) -
+        (this.$$('action-toolbar').offsetWidth / 2);
+    this.style.setProperty('--action-toolbar-left', leftPosition + 'px');
+  },
 });
diff --git a/ash/webui/scanning/resources/scanning_app.html b/ash/webui/scanning/resources/scanning_app.html
index 8e4db3e5..c588c66 100644
--- a/ash/webui/scanning/resources/scanning_app.html
+++ b/ash/webui/scanning/resources/scanning_app.html
@@ -196,8 +196,10 @@
           <file-type-select id="fileTypeSelect"
               disabled="[[settingsDisabled_]]"
               selected-file-type="{{selectedFileType}}"></file-type-select>
-          <multi-page-checkbox id="multiPageCheckbox" hidden="[[!showMultiPageCheckbox_]]"
-              multi-page-scan-checked="{{multiPageScanChecked}}">
+          <multi-page-checkbox id="multiPageCheckbox"
+              hidden="[[!showMultiPageCheckbox_]]"
+              multi-page-scan-checked="{{multiPageScanChecked}}"
+              disabled="[[settingsDisabled_]]">
           </multi-page-checkbox>
           <div id="more-settings-line-separator"></div>
           <cr-button id="moreSettingsButton" on-click="toggleClicked_"
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service.cc b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
index 0e16f5a0..b0092fc 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service.cc
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
@@ -73,9 +73,38 @@
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
-void ShimlessRmaService::CheckForNetworkConnection(
-    CheckForNetworkConnectionCallback callback) {
-  // TODO(joonbug): actually check for network
+void ShimlessRmaService::BeginFinalization(BeginFinalizationCallback callback) {
+  if (state_proto_.state_case() != rmad::RmadState::kWelcome) {
+    LOG(ERROR) << "FinalizeRepair called from incorrect state "
+               << state_proto_.state_case();
+    std::move(callback).Run(state_proto_.state_case(),
+                            rmad::RmadErrorCode::RMAD_ERROR_REQUEST_INVALID);
+    return;
+  }
+  state_proto_.mutable_welcome()->set_choice(
+      rmad::WelcomeState::RMAD_CHOICE_FINALIZE_REPAIR);
+  // TODO(gavindodd): Determine if rmad service or shimless rma is responsible
+  // for managing the network connection and chrome update states.
+  // If shimless rma is responsible then check network connection here and
+  // insert a non-rmad state here to display the network select screen.
+  TransitionNextStateGeneric(std::move(callback));
+}
+
+void ShimlessRmaService::NetworkSelectionComplete(
+    NetworkSelectionCompleteCallback callback) {
+  if (state_proto_.state_case() != rmad::RmadState::kSelectNetwork) {
+    LOG(ERROR) << "FinalizeRepair called from incorrect state "
+               << state_proto_.state_case();
+    std::move(callback).Run(state_proto_.state_case(),
+                            rmad::RmadErrorCode::RMAD_ERROR_REQUEST_INVALID);
+    return;
+  }
+  // TODO(gavindodd): Determine network state and set correct connection state.
+  // Should this be done on JS side with a separate call for user declined?
+  // That way we can confirm it was an active user choice rather than a
+  // disconnect when next was clicked?
+  state_proto_.mutable_select_network()->set_connection_state(
+      rmad::SelectNetworkState::RMAD_NETWORK_CONNECTED);
   TransitionNextStateGeneric(std::move(callback));
 }
 
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service.h b/ash/webui/shimless_rma/backend/shimless_rma_service.h
index cc340a4..6a36e29 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service.h
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service.h
@@ -34,8 +34,11 @@
 
   void AbortRma(AbortRmaCallback callback) override;
 
-  void CheckForNetworkConnection(
-      CheckForNetworkConnectionCallback callback) override;
+  void BeginFinalization(BeginFinalizationCallback callback) override;
+
+  void NetworkSelectionComplete(
+      NetworkSelectionCompleteCallback callback) override;
+
   void GetCurrentChromeVersion(
       GetCurrentChromeVersionCallback callback) override;
   void CheckForChromeUpdates(CheckForChromeUpdatesCallback callback) override;
diff --git a/ash/webui/shimless_rma/mojom/shimless_rma.mojom b/ash/webui/shimless_rma/mojom/shimless_rma.mojom
index 0c7e8a9..a731ae3 100644
--- a/ash/webui/shimless_rma/mojom/shimless_rma.mojom
+++ b/ash/webui/shimless_rma/mojom/shimless_rma.mojom
@@ -257,8 +257,19 @@
   // Attempts to abort the RMA.
   AbortRma() => (RmadErrorCode error);
 
-  // Checks for network connection and returns appropriate RMA state.
-  CheckForNetworkConnection() => (RmaState state, RmadErrorCode error);
+
+  ///////////////////////////////////////
+  // Methods for kWelcomeScreen state.
+  //
+  // User has confirmed they wish to finalize RMA.
+  BeginFinalization() => (RmaState state, RmadErrorCode error);
+
+  ///////////////////////////////////////
+  // Methods for kConfigureNetwork state.
+  //
+  // Called when next is clicked after a network is successfully connected or
+  // the user skips connecting to a network.
+  NetworkSelectionComplete() => (RmaState state, RmadErrorCode error);
 
   ///////////////////////////////////////
   // Methods for kUpdateChrome state.
diff --git a/ash/webui/shimless_rma/resources/fake_shimless_rma_service.js b/ash/webui/shimless_rma/resources/fake_shimless_rma_service.js
index e2012e0..6602299 100644
--- a/ash/webui/shimless_rma/resources/fake_shimless_rma_service.js
+++ b/ash/webui/shimless_rma/resources/fake_shimless_rma_service.js
@@ -144,29 +144,17 @@
   /**
    * @return {!Promise<!StateResult>}
    */
-  checkForNetworkConnection() {
-    const resolver = new PromiseResolver();
-    this.stateIndex_++;
-    this.methods_.resolveMethod('checkForNetworkConnection')
-        .then((nextState) => {
-          if (nextState.state === RmaState.kUpdateChrome) {
-            this.stateIndex_++;
-          }
-          resolver.resolve(nextState);
-        });
-    return resolver.promise;
+  beginFinalization() {
+    return this.getNextStateForMethod_(
+        'beginFinalization', RmaState.kWelcomeScreen);
   }
 
   /**
-   * Sets the return value of checkForNetworkConnection() which is the
-   * next state (either network selection page or the step afterwards).
-   * @param {!StateResult} nextState
+   * @return {!Promise<!StateResult>}
    */
-  setCheckForNetworkConnection(nextState) {
-    assert(
-        nextState.state === RmaState.kUpdateChrome ||
-        nextState.state === RmaState.kConfigureNetwork);
-    this.methods_.setResult('checkForNetworkConnection', nextState);
+  networkSelectionComplete() {
+    return this.getNextStateForMethod_(
+        'networkSelectionComplete', RmaState.kConfigureNetwork);
   }
 
   /**
@@ -644,7 +632,10 @@
 
     this.methods_.register('abortRma');
 
-    this.methods_.register('checkForNetworkConnection');
+    this.methods_.register('beginFinalization');
+
+    this.methods_.register('networkSelectionComplete');
+
     this.methods_.register('getCurrentChromeVersion');
     this.methods_.register('checkForChromeUpdates');
     this.methods_.register('updateChrome');
diff --git a/ash/webui/shimless_rma/resources/mojo_interface_provider.js b/ash/webui/shimless_rma/resources/mojo_interface_provider.js
index 4c9bc7f..c70428c 100644
--- a/ash/webui/shimless_rma/resources/mojo_interface_provider.js
+++ b/ash/webui/shimless_rma/resources/mojo_interface_provider.js
@@ -45,7 +45,6 @@
 
   service.setGetComponentListResult(fakeComponents);
   service.setReimageRequiredResult(false);
-  service.setCheckForNetworkConnection(fakeStates[2]);
   service.automaticallyTriggerDisableWriteProtectionObservation();
   service.automaticallyTriggerProvisioningObservation();
 
diff --git a/ash/webui/shimless_rma/resources/onboarding_landing_page.js b/ash/webui/shimless_rma/resources/onboarding_landing_page.js
index 63efe39..b8318ff 100644
--- a/ash/webui/shimless_rma/resources/onboarding_landing_page.js
+++ b/ash/webui/shimless_rma/resources/onboarding_landing_page.js
@@ -8,7 +8,7 @@
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getShimlessRmaService} from './mojo_interface_provider.js';
-import {RmadErrorCode, RmaState, ShimlessRmaServiceInterface, StateResult} from './shimless_rma_types.js';
+import {ShimlessRmaServiceInterface, StateResult} from './shimless_rma_types.js';
 
 /**
  * @fileoverview
@@ -31,12 +31,6 @@
         type: Object,
         value: null,
       },
-
-      /** @private {boolean} */
-      networkConnected_: {
-        type: Boolean,
-        value: false,
-      },
     };
   }
 
@@ -48,7 +42,7 @@
 
   /** @return {!Promise<StateResult>} */
   onNextButtonClick() {
-    return this.shimlessRmaService_.checkForNetworkConnection();
+    return this.shimlessRmaService_.beginFinalization();
   }
 };
 
diff --git a/ash/webui/shimless_rma/resources/onboarding_network_page.js b/ash/webui/shimless_rma/resources/onboarding_network_page.js
index 3555bbe0..396a6ee 100644
--- a/ash/webui/shimless_rma/resources/onboarding_network_page.js
+++ b/ash/webui/shimless_rma/resources/onboarding_network_page.js
@@ -17,8 +17,8 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {getNetworkConfigService} from './mojo_interface_provider.js';
-import {NetworkConfigServiceInterface} from './shimless_rma_types.js';
+import {getNetworkConfigService, getShimlessRmaService} from './mojo_interface_provider.js';
+import {NetworkConfigServiceInterface, ShimlessRmaServiceInterface, StateResult} from './shimless_rma_types.js';
 
 /**
  * @fileoverview
@@ -48,6 +48,12 @@
 
   static get properties() {
     return {
+      /** @private {ShimlessRmaServiceInterface} */
+      shimlessRmaService_: {
+        type: Object,
+        value: null,
+      },
+
       /** @private {?NetworkConfigServiceInterface} */
       networkConfig_: {
         type: Object,
@@ -129,6 +135,7 @@
   /** @override */
   ready() {
     super.ready();
+    this.shimlessRmaService_ = getShimlessRmaService();
     this.networkConfig_ = getNetworkConfigService();
     this.refreshNetworks();
   }
@@ -285,6 +292,11 @@
     return type;
     // return this.i18n('internetJoinType', type);
   }
+
+  /** @return {!Promise<StateResult>} */
+  onNextButtonClick() {
+    return this.shimlessRmaService_.networkSelectionComplete();
+  }
 };
 
 customElements.define(OnboardingNetworkPage.is, OnboardingNetworkPage);
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 0c47ed7..d82c878 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -878,6 +878,7 @@
     sources += [
       "debug/debugger_posix.cc",
       "debug/stack_trace_posix.cc",
+      "file_descriptor_posix.cc",
       "file_descriptor_posix.h",
       "files/dir_reader_posix.h",
       "files/file_descriptor_watcher_posix.cc",
@@ -1618,6 +1619,7 @@
       "debug/elf_reader.cc",
       "debug/elf_reader.h",
       "debug/stack_trace_fuchsia.cc",
+      "file_descriptor_posix.cc",
       "file_descriptor_posix.h",
       "files/dir_reader_posix.h",
       "files/file_descriptor_watcher_posix.cc",
@@ -2364,10 +2366,6 @@
         "trace_event/trace_event_android.cc",
       ]
     }
-
-    if (use_perfetto_client_library) {
-      sources -= [ "trace_event/builtin_categories.cc" ]
-    }
   } else {
     sources += [
       "trace_event/trace_event_stub.cc",
diff --git a/base/allocator/partition_allocator/partition_alloc_unittest.cc b/base/allocator/partition_allocator/partition_alloc_unittest.cc
index 7fda2c3..fd3254a 100644
--- a/base/allocator/partition_allocator/partition_alloc_unittest.cc
+++ b/base/allocator/partition_allocator/partition_alloc_unittest.cc
@@ -2838,6 +2838,10 @@
   }
 }
 
+TEST_F(PartitionAllocTest, GetUsableSizeNull) {
+  EXPECT_EQ(0ULL, PartitionRoot<ThreadSafe>::GetUsableSize(nullptr));
+}
+
 TEST_F(PartitionAllocTest, GetUsableSize) {
   size_t delta = SystemPageSize() + 1;
   for (size_t size = 1; size <= kMinDirectMappedDownsize; size += delta) {
diff --git a/base/allocator/partition_allocator/partition_root.h b/base/allocator/partition_allocator/partition_root.h
index 809cbfc..6690f7b 100644
--- a/base/allocator/partition_allocator/partition_root.h
+++ b/base/allocator/partition_allocator/partition_root.h
@@ -1168,6 +1168,9 @@
 // PartitionAlloc's internal data. Used as malloc_usable_size.
 template <bool thread_safe>
 ALWAYS_INLINE size_t PartitionRoot<thread_safe>::GetUsableSize(void* ptr) {
+  // malloc_usable_size() is expected to handle NULL gracefully and return 0.
+  if (!ptr)
+    return 0;
   auto* slot_span = SlotSpan::FromSlotInnerPtr(ptr);
   auto* root = FromSlotSpan(slot_span);
   return slot_span->GetUsableSize(root);
diff --git a/base/allocator/partition_allocator/starscan/pcscan_internal.cc b/base/allocator/partition_allocator/starscan/pcscan_internal.cc
index 5a42cd2..95f9597c 100644
--- a/base/allocator/partition_allocator/starscan/pcscan_internal.cc
+++ b/base/allocator/partition_allocator/starscan/pcscan_internal.cc
@@ -404,7 +404,7 @@
         ++current;
       });
 
-  PA_DCHECK(kMaxSlotSpansInSuperPage > current);
+  PA_DCHECK(kMaxSlotSpansInSuperPage >= current);
   scan_areas_.set_size(current);
 }
 
diff --git a/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
index 4d16b00..16126e8 100644
--- a/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
+++ b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
@@ -526,7 +526,7 @@
     }
 
     /**
-     * Retrieves an image for the given url as a Bitmap.
+     * Retrieves an image for the given uri as a Bitmap.
      */
     public static Bitmap getBitmapByUri(ContentResolver cr, Uri uri) throws IOException {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
diff --git a/base/android/java/src/org/chromium/base/ApplicationStatus.java b/base/android/java/src/org/chromium/base/ApplicationStatus.java
index 0fdaf86..fb03457 100644
--- a/base/android/java/src/org/chromium/base/ApplicationStatus.java
+++ b/base/android/java/src/org/chromium/base/ApplicationStatus.java
@@ -96,22 +96,19 @@
     /**
      * A list of observers to be notified when any {@link Activity} has a state change.
      */
-    private static final ObserverList<ActivityStateListener> sGeneralActivityStateListeners =
-            new ObserverList<>();
+    private static ObserverList<ActivityStateListener> sGeneralActivityStateListeners;
 
     /**
      * A list of observers to be notified when the visibility state of this {@link Application}
      * changes.  See {@link #getStateForApplication()}.
      */
-    private static final ObserverList<ApplicationStateListener> sApplicationStateListeners =
-            new ObserverList<>();
+    private static ObserverList<ApplicationStateListener> sApplicationStateListeners;
 
     /**
      * A list of observers to be notified when the window focus changes.
      * See {@link #registerWindowFocusChangedListener}.
      */
-    private static final ObserverList<WindowFocusChangedListener> sWindowFocusListeners =
-            new ObserverList<>();
+    private static ObserverList<WindowFocusChangedListener> sWindowFocusListeners;
 
     /**
      * Interface to be implemented by listeners.
@@ -157,6 +154,7 @@
     @MainThread
     public static void registerWindowFocusChangedListener(WindowFocusChangedListener listener) {
         assert isInitialized();
+        if (sWindowFocusListeners == null) sWindowFocusListeners = new ObserverList<>();
         sWindowFocusListeners.addObserver(listener);
     }
 
@@ -166,6 +164,7 @@
      */
     @MainThread
     public static void unregisterWindowFocusChangedListener(WindowFocusChangedListener listener) {
+        if (sWindowFocusListeners == null) return;
         sWindowFocusListeners.removeObserver(listener);
     }
 
@@ -213,8 +212,10 @@
         public void onWindowFocusChanged(boolean hasFocus) {
             mCallback.onWindowFocusChanged(hasFocus);
 
-            for (WindowFocusChangedListener listener : sWindowFocusListeners) {
-                listener.onWindowFocusChanged(mActivity, hasFocus);
+            if (sWindowFocusListeners != null) {
+                for (WindowFocusChangedListener listener : sWindowFocusListeners) {
+                    listener.onWindowFocusChanged(mActivity, hasFocus);
+                }
             }
         }
     }
@@ -397,12 +398,14 @@
 
         // Notify all state observers that are listening globally for all activity state
         // changes.
-        for (ActivityStateListener listener : sGeneralActivityStateListeners) {
-            listener.onActivityStateChange(activity, newState);
+        if (sGeneralActivityStateListeners != null) {
+            for (ActivityStateListener listener : sGeneralActivityStateListeners) {
+                listener.onActivityStateChange(activity, newState);
+            }
         }
 
         int applicationState = getStateForApplication();
-        if (applicationState != oldApplicationState) {
+        if (applicationState != oldApplicationState && sApplicationStateListeners != null) {
             for (ApplicationStateListener listener : sApplicationStateListeners) {
                 listener.onApplicationStateChange(applicationState);
             }
@@ -534,6 +537,9 @@
     @MainThread
     public static void registerStateListenerForAllActivities(ActivityStateListener listener) {
         assert isInitialized();
+        if (sGeneralActivityStateListeners == null) {
+            sGeneralActivityStateListeners = new ObserverList<>();
+        }
         sGeneralActivityStateListeners.addObserver(listener);
     }
 
@@ -563,7 +569,9 @@
      */
     @MainThread
     public static void unregisterActivityStateListener(ActivityStateListener listener) {
-        sGeneralActivityStateListeners.removeObserver(listener);
+        if (sGeneralActivityStateListeners != null) {
+            sGeneralActivityStateListeners.removeObserver(listener);
+        }
 
         // Loop through all observer lists for all activities and remove the listener.
         synchronized (sActivityInfo) {
@@ -579,6 +587,9 @@
      */
     @MainThread
     public static void registerApplicationStateListener(ApplicationStateListener listener) {
+        if (sApplicationStateListeners == null) {
+            sApplicationStateListeners = new ObserverList<>();
+        }
         sApplicationStateListeners.addObserver(listener);
     }
 
@@ -588,6 +599,7 @@
      */
     @MainThread
     public static void unregisterApplicationStateListener(ApplicationStateListener listener) {
+        if (sApplicationStateListeners == null) return;
         sApplicationStateListeners.removeObserver(listener);
     }
 
@@ -599,10 +611,10 @@
     @MainThread
     public static void destroyForJUnitTests() {
         synchronized (sActivityInfo) {
-            sApplicationStateListeners.clear();
-            sGeneralActivityStateListeners.clear();
+            if (sApplicationStateListeners != null) sApplicationStateListeners.clear();
+            if (sGeneralActivityStateListeners != null) sGeneralActivityStateListeners.clear();
             sActivityInfo.clear();
-            sWindowFocusListeners.clear();
+            if (sWindowFocusListeners != null) sWindowFocusListeners.clear();
             sCurrentApplicationState = ApplicationState.UNKNOWN;
             sActivity = null;
             sNativeApplicationStateListener = null;
diff --git a/base/android/java/src/org/chromium/base/ObserverList.java b/base/android/java/src/org/chromium/base/ObserverList.java
index 59276c6..9a3e2b6 100644
--- a/base/android/java/src/org/chromium/base/ObserverList.java
+++ b/base/android/java/src/org/chromium/base/ObserverList.java
@@ -30,6 +30,8 @@
  */
 @NotThreadSafe
 public class ObserverList<E> implements Iterable<E> {
+    private static final boolean ENABLE_THREAD_ASSERTS = false;
+
     /**
      * Extended iterator interface that provides rewind functionality.
      */
@@ -44,11 +46,14 @@
     }
 
     public final List<E> mObservers = new ArrayList<E>();
+    private final ThreadUtils.ThreadChecker mThreadChecker;
     private int mIterationDepth;
     private int mCount;
     private boolean mNeedsCompact;
 
-    public ObserverList() {}
+    public ObserverList() {
+        mThreadChecker = new ThreadUtils.ThreadChecker();
+    }
 
     /**
      * Add an observer to the list.
@@ -59,6 +64,8 @@
      * @return true if the observer list changed as a result of the call.
      */
     public boolean addObserver(E obs) {
+        if (ENABLE_THREAD_ASSERTS) mThreadChecker.assertOnValidThread();
+
         // Avoid adding null elements to the list as they may be removed on a compaction.
         if (obs == null || mObservers.contains(obs)) {
             return false;
@@ -79,6 +86,8 @@
      * @return true if an element was removed as a result of this call.
      */
     public boolean removeObserver(E obs) {
+        if (ENABLE_THREAD_ASSERTS) mThreadChecker.assertOnValidThread();
+
         if (obs == null) {
             return false;
         }
@@ -102,10 +111,14 @@
     }
 
     public boolean hasObserver(E obs) {
+        if (ENABLE_THREAD_ASSERTS) mThreadChecker.assertOnValidThread();
+
         return mObservers.contains(obs);
     }
 
     public void clear() {
+        if (ENABLE_THREAD_ASSERTS) mThreadChecker.assertOnValidThread();
+
         mCount = 0;
 
         if (mIterationDepth == 0) {
@@ -122,6 +135,8 @@
 
     @Override
     public Iterator<E> iterator() {
+        if (ENABLE_THREAD_ASSERTS) mThreadChecker.assertOnValidThread();
+
         return new ObserverListIterator();
     }
 
@@ -131,6 +146,8 @@
      * {@link RewindableIterator#rewind()}.
      */
     public RewindableIterator<E> rewindableIterator() {
+        if (ENABLE_THREAD_ASSERTS) mThreadChecker.assertOnValidThread();
+
         return new ObserverListIterator();
     }
 
@@ -139,6 +156,8 @@
      * This is equivalent to the number of non-empty spaces in |mObservers|.
      */
     public int size() {
+        if (ENABLE_THREAD_ASSERTS) mThreadChecker.assertOnValidThread();
+
         return mCount;
     }
 
@@ -146,6 +165,8 @@
      * Returns true if the ObserverList contains no observers.
      */
     public boolean isEmpty() {
+        if (ENABLE_THREAD_ASSERTS) mThreadChecker.assertOnValidThread();
+
         return mCount == 0;
     }
 
@@ -200,6 +221,8 @@
 
         @Override
         public void rewind() {
+            if (ENABLE_THREAD_ASSERTS) mThreadChecker.assertOnValidThread();
+
             compactListIfNeeded();
             ObserverList.this.incrementIterationDepth();
             mListEndMarker = ObserverList.this.capacity();
@@ -209,6 +232,8 @@
 
         @Override
         public boolean hasNext() {
+            if (ENABLE_THREAD_ASSERTS) mThreadChecker.assertOnValidThread();
+
             int lookupIndex = mIndex;
             while (lookupIndex < mListEndMarker
                     && ObserverList.this.getObserverAt(lookupIndex) == null) {
@@ -223,6 +248,8 @@
 
         @Override
         public E next() {
+            if (ENABLE_THREAD_ASSERTS) mThreadChecker.assertOnValidThread();
+
             // Advance if the current element is null.
             while (mIndex < mListEndMarker && ObserverList.this.getObserverAt(mIndex) == null) {
                 mIndex++;
diff --git a/base/android/jni_android_unittest.cc b/base/android/jni_android_unittest.cc
index 3f25775..30dc0c7 100644
--- a/base/android/jni_android_unittest.cc
+++ b/base/android/jni_android_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/at_exit.h"
 #include "base/logging.h"
+#include "base/time/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
diff --git a/base/file_descriptor_posix.cc b/base/file_descriptor_posix.cc
new file mode 100644
index 0000000..ca5922d
--- /dev/null
+++ b/base/file_descriptor_posix.cc
@@ -0,0 +1,34 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/file_descriptor_posix.h"
+
+#include "base/files/file.h"
+
+namespace base {
+
+FileDescriptor::FileDescriptor() = default;
+
+FileDescriptor::FileDescriptor(int ifd, bool iauto_close)
+    : fd(ifd), auto_close(iauto_close) {}
+
+FileDescriptor::FileDescriptor(File file)
+    : fd(file.TakePlatformFile()), auto_close(true) {}
+
+FileDescriptor::FileDescriptor(ScopedFD fd)
+    : fd(fd.release()), auto_close(true) {}
+
+bool FileDescriptor::operator==(const FileDescriptor& other) const {
+  return fd == other.fd && auto_close == other.auto_close;
+}
+
+bool FileDescriptor::operator!=(const FileDescriptor& other) const {
+  return !operator==(other);
+}
+
+bool FileDescriptor::operator<(const FileDescriptor& other) const {
+  return other.fd < fd;
+}
+
+}  // namespace base
\ No newline at end of file
diff --git a/base/file_descriptor_posix.h b/base/file_descriptor_posix.h
index f01583a0..d68377f 100644
--- a/base/file_descriptor_posix.h
+++ b/base/file_descriptor_posix.h
@@ -5,11 +5,13 @@
 #ifndef BASE_FILE_DESCRIPTOR_POSIX_H_
 #define BASE_FILE_DESCRIPTOR_POSIX_H_
 
-#include "base/files/file.h"
+#include "base/base_export.h"
 #include "base/files/scoped_file.h"
 
 namespace base {
 
+class File;
+
 constexpr int kInvalidFd = -1;
 
 // -----------------------------------------------------------------------------
@@ -27,33 +29,24 @@
 // processing the IPC message. See the IPC::ParamTraits<> specialization in
 // ipc/ipc_message_utils.h for all the details.
 // -----------------------------------------------------------------------------
-struct FileDescriptor {
-  FileDescriptor() : fd(kInvalidFd), auto_close(false) {}
+struct BASE_EXPORT FileDescriptor {
+  FileDescriptor();
+  FileDescriptor(int ifd, bool iauto_close);
+  explicit FileDescriptor(File file);
+  explicit FileDescriptor(ScopedFD fd);
 
-  FileDescriptor(int ifd, bool iauto_close) : fd(ifd), auto_close(iauto_close) {
-  }
-
-  FileDescriptor(File file) : fd(file.TakePlatformFile()), auto_close(true) {}
-  explicit FileDescriptor(ScopedFD fd) : fd(fd.release()), auto_close(true) {}
-
-  bool operator==(const FileDescriptor& other) const {
-    return (fd == other.fd && auto_close == other.auto_close);
-  }
-
-  bool operator!=(const FileDescriptor& other) const {
-    return !operator==(other);
-  }
+  bool operator==(const FileDescriptor& other) const;
+  bool operator!=(const FileDescriptor& other) const;
 
   // A comparison operator so that we can use these as keys in a std::map.
-  bool operator<(const FileDescriptor& other) const {
-    return other.fd < fd;
-  }
+  bool operator<(const FileDescriptor& other) const;
 
-  int fd;
+  int fd = kInvalidFd;
+
   // If true, this file descriptor should be closed after it has been used. For
   // example an IPC system might interpret this flag as indicating that the
   // file descriptor it has been given should be closed after use.
-  bool auto_close;
+  bool auto_close = false;
 };
 
 }  // namespace base
diff --git a/base/files/file.h b/base/files/file.h
index 5f248d6..8b62129 100644
--- a/base/files/file.h
+++ b/base/files/file.h
@@ -179,8 +179,8 @@
   void Initialize(const FilePath& path, uint32_t flags);
 
   // Returns |true| if the handle / fd wrapped by this object is valid.  This
-  // method doesn't interact with the file system (and is safe to be called from
-  // ThreadRestrictions::SetIOAllowed(false) threads).
+  // method doesn't interact with the file system and is thus safe to be called
+  // from threads that disallow blocking.
   bool IsValid() const;
 
   // Returns true if a new file was created (or an old one truncated to zero
diff --git a/base/files/file_proxy_unittest.cc b/base/files/file_proxy_unittest.cc
index c9f837af..ad4f7c1 100644
--- a/base/files/file_proxy_unittest.cc
+++ b/base/files/file_proxy_unittest.cc
@@ -160,17 +160,18 @@
 }
 
 TEST_F(FileProxyTest, CreateOrOpen_AbandonedCreate) {
-  bool prev = ThreadRestrictions::SetIOAllowed(false);
-  RunLoop run_loop;
   {
-    FileProxy proxy(file_task_runner());
-    proxy.CreateOrOpen(
-        TestPath(), File::FLAG_CREATE | File::FLAG_READ,
-        BindOnce(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr(),
-                 run_loop.QuitWhenIdleClosure()));
+    base::ScopedDisallowBlocking disallow_blocking;
+    RunLoop run_loop;
+    {
+      FileProxy proxy(file_task_runner());
+      proxy.CreateOrOpen(
+          TestPath(), File::FLAG_CREATE | File::FLAG_READ,
+          BindOnce(&FileProxyTest::DidCreateOrOpen, weak_factory_.GetWeakPtr(),
+                   run_loop.QuitWhenIdleClosure()));
+    }
+    run_loop.Run();
   }
-  run_loop.Run();
-  ThreadRestrictions::SetIOAllowed(prev);
 
   EXPECT_TRUE(PathExists(TestPath()));
 }
diff --git a/base/nix/mime_util_xdg.h b/base/nix/mime_util_xdg.h
index e0f264a..c583abc6 100644
--- a/base/nix/mime_util_xdg.h
+++ b/base/nix/mime_util_xdg.h
@@ -25,7 +25,7 @@
 //
 // Note that this function might need to read from disk the mime-types data
 // provided by the OS.  Therefore this function should not be called from
-// threads that disallow IO via base::ThreadRestrictions::SetIOAllowed(false).
+// threads that disallow blocking.
 //
 // If the mime type is unknown, this will return application/octet-stream.
 BASE_EXPORT std::string GetFileMimeType(const FilePath& filepath);
diff --git a/base/synchronization/lock.h b/base/synchronization/lock.h
index 3f180bd..0190254 100644
--- a/base/synchronization/lock.h
+++ b/base/synchronization/lock.h
@@ -7,12 +7,16 @@
 
 #include "base/base_export.h"
 #include "base/check_op.h"
+#include "base/dcheck_is_on.h"
 #include "base/macros.h"
 #include "base/synchronization/lock_impl.h"
 #include "base/thread_annotations.h"
-#include "base/threading/platform_thread.h"
 #include "build/build_config.h"
 
+#if DCHECK_IS_ON()
+#include "base/threading/platform_thread.h"
+#endif
+
 namespace base {
 
 // A convenient wrapper for an OS specific critical section.  The only real
diff --git a/base/task/sequence_manager/sequence_manager_impl_unittest.cc b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
index ddaf1c6..e0e3f9eb 100644
--- a/base/task/sequence_manager/sequence_manager_impl_unittest.cc
+++ b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
@@ -2324,49 +2324,74 @@
 
 namespace {
 
-class MockTaskQueueObserver : public TaskQueue::Observer {
+class MockTaskQueueThrottler : public TaskQueue::Throttler {
  public:
-  ~MockTaskQueueObserver() override = default;
+  TaskQueue* task_queue;
 
-  MOCK_METHOD1(OnQueueNextWakeUpChanged, void(TimeTicks));
+  explicit MockTaskQueueThrottler(TaskQueue* task_queue)
+      : task_queue(task_queue) {}
+  ~MockTaskQueueThrottler() = default;
+
+  MOCK_METHOD1(OnWakeUp, void(LazyNow*));
+  MOCK_METHOD0(OnHasImmediateTask, void());
+
+  MOCK_METHOD1(GetNextAllowedWakeUp_DesiredWakeUpTime, void(TimeTicks));
+
+  absl::optional<DelayedWakeUp> GetNextAllowedWakeUp(
+      LazyNow* lazy_now,
+      absl::optional<DelayedWakeUp> next_desired_wake_up,
+      bool has_immediate_work) override {
+    if (next_desired_wake_up)
+      GetNextAllowedWakeUp_DesiredWakeUpTime(next_desired_wake_up->time);
+    if (next_allowed_wake_up_)
+      return next_allowed_wake_up_;
+    return next_desired_wake_up;
+  }
+
+  void SetNextAllowedWakeUp(
+      absl::optional<DelayedWakeUp> next_allowed_wake_up) {
+    next_allowed_wake_up_ = next_allowed_wake_up;
+  }
+
+ private:
+  absl::optional<DelayedWakeUp> next_allowed_wake_up_;
 };
 
 }  // namespace
 
-TEST_P(SequenceManagerTest, TaskQueueObserver_ImmediateTask) {
+TEST_P(SequenceManagerTest, TaskQueueThrottler_ImmediateTask) {
   auto queue = CreateTaskQueue();
 
-  MockTaskQueueObserver observer;
-  queue->SetObserver(&observer);
+  StrictMock<MockTaskQueueThrottler> throttler(queue.get());
+  queue->SetThrottler(&throttler);
 
-  // We should get a OnQueueNextWakeUpChanged notification when a task is posted
-  // on an empty queue.
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_));
+  // OnHasImmediateTask should be called when a task is posted on an empty
+  // queue.
+  EXPECT_CALL(throttler, OnHasImmediateTask());
   queue->task_runner()->PostTask(FROM_HERE, BindOnce(&NopTask));
   sequence_manager()->ReloadEmptyWorkQueues();
-  Mock::VerifyAndClearExpectations(&observer);
+  Mock::VerifyAndClearExpectations(&throttler);
 
   // But not subsequently.
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_)).Times(0);
   queue->task_runner()->PostTask(FROM_HERE, BindOnce(&NopTask));
   sequence_manager()->ReloadEmptyWorkQueues();
-  Mock::VerifyAndClearExpectations(&observer);
+  Mock::VerifyAndClearExpectations(&throttler);
 
   // Unless the immediate work queue is emptied.
   sequence_manager()->SelectNextTask();
   sequence_manager()->DidRunTask();
   sequence_manager()->SelectNextTask();
   sequence_manager()->DidRunTask();
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_));
+  EXPECT_CALL(throttler, OnHasImmediateTask());
   queue->task_runner()->PostTask(FROM_HERE, BindOnce(&NopTask));
   sequence_manager()->ReloadEmptyWorkQueues();
-  Mock::VerifyAndClearExpectations(&observer);
+  Mock::VerifyAndClearExpectations(&throttler);
 
   // Tidy up.
   queue->ShutdownTaskQueue();
 }
 
-TEST_P(SequenceManagerTest, TaskQueueObserver_DelayedTask) {
+TEST_P(SequenceManagerTest, TaskQueueThrottler_DelayedTask) {
   auto queue = CreateTaskQueue();
 
   TimeTicks start_time = sequence_manager()->NowTicks();
@@ -2374,65 +2399,133 @@
   TimeDelta delay100s(TimeDelta::FromSeconds(100));
   TimeDelta delay1s(TimeDelta::FromSeconds(1));
 
-  MockTaskQueueObserver observer;
-  queue->SetObserver(&observer);
+  StrictMock<MockTaskQueueThrottler> throttler(queue.get());
+  queue->SetThrottler(&throttler);
 
-  // We should get OnQueueNextWakeUpChanged notification when a delayed task is
-  // is posted on an empty queue.
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(start_time + delay10s));
+  // GetNextAllowedWakeUp should be called when a delayed task is posted on an
+  // empty queue.
+  EXPECT_CALL(throttler,
+              GetNextAllowedWakeUp_DesiredWakeUpTime(start_time + delay10s));
   queue->task_runner()->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
                                         delay10s);
-  Mock::VerifyAndClearExpectations(&observer);
+  Mock::VerifyAndClearExpectations(&throttler);
 
-  // We should not get an OnQueueNextWakeUpChanged notification for a longer
-  // delay.
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_)).Times(0);
+  // GetNextAllowedWakeUp should be given the same delay when a longer delay
+  // task is posted.
+  EXPECT_CALL(throttler,
+              GetNextAllowedWakeUp_DesiredWakeUpTime(start_time + delay10s));
   queue->task_runner()->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
                                         delay100s);
-  Mock::VerifyAndClearExpectations(&observer);
+  Mock::VerifyAndClearExpectations(&throttler);
 
-  // We should get an OnQueueNextWakeUpChanged notification for a shorter delay.
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(start_time + delay1s));
+  // GetNextAllowedWakeUp should be given the new delay when a task is posted
+  // with a shorter delay.
+  EXPECT_CALL(throttler,
+              GetNextAllowedWakeUp_DesiredWakeUpTime(start_time + delay1s));
   queue->task_runner()->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay1s);
-  Mock::VerifyAndClearExpectations(&observer);
+  Mock::VerifyAndClearExpectations(&throttler);
 
   std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
       queue->CreateQueueEnabledVoter();
   voter->SetVoteToEnable(false);
-  Mock::VerifyAndClearExpectations(&observer);
+  Mock::VerifyAndClearExpectations(&throttler);
 
   // When a queue has been enabled, we may get a notification if the
   // TimeDomain's next scheduled wake-up has changed.
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(start_time + delay1s));
+  EXPECT_CALL(throttler,
+              GetNextAllowedWakeUp_DesiredWakeUpTime(start_time + delay1s));
   voter->SetVoteToEnable(true);
-  Mock::VerifyAndClearExpectations(&observer);
+  Mock::VerifyAndClearExpectations(&throttler);
 
   // Tidy up.
   queue->ShutdownTaskQueue();
 }
 
-TEST_P(SequenceManagerTest, TaskQueueObserver_DelayedTaskMultipleQueues) {
+TEST_P(SequenceManagerTest, TaskQueueThrottler_OnWakeUp) {
+  auto queue = CreateTaskQueue();
+
+  TimeTicks start_time = sequence_manager()->NowTicks();
+  TimeDelta delay(TimeDelta::FromSeconds(1));
+
+  StrictMock<MockTaskQueueThrottler> throttler(queue.get());
+  queue->SetThrottler(&throttler);
+
+  EXPECT_CALL(throttler,
+              GetNextAllowedWakeUp_DesiredWakeUpTime(start_time + delay));
+  queue->task_runner()->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay);
+  Mock::VerifyAndClearExpectations(&throttler);
+
+  AdvanceMockTickClock(delay);
+
+  // OnWakeUp should be called when the queue has a scheduler wake up.
+  EXPECT_CALL(throttler, OnWakeUp(_));
+  // Move the task into the |delayed_work_queue|.
+  LazyNow lazy_now(mock_tick_clock());
+  sequence_manager()->MoveReadyDelayedTasksToWorkQueues(&lazy_now);
+  Mock::VerifyAndClearExpectations(&throttler);
+
+  // Tidy up.
+  queue->ShutdownTaskQueue();
+}
+
+TEST_P(SequenceManagerTest, TaskQueueThrottler_ResetThrottler) {
+  auto queue = CreateTaskQueue();
+
+  TimeTicks start_time = sequence_manager()->NowTicks();
+  TimeDelta delay10s(TimeDelta::FromSeconds(10));
+  TimeDelta delay1s(TimeDelta::FromSeconds(1));
+
+  StrictMock<MockTaskQueueThrottler> throttler(queue.get());
+  queue->SetThrottler(&throttler);
+  EXPECT_FALSE(queue->GetNextDesiredWakeUp());
+
+  // GetNextAllowedWakeUp should be called when a delayed task is posted on an
+  // empty queue.
+  throttler.SetNextAllowedWakeUp(
+      base::sequence_manager::DelayedWakeUp{start_time + delay10s});
+  EXPECT_CALL(throttler,
+              GetNextAllowedWakeUp_DesiredWakeUpTime(start_time + delay1s));
+  queue->task_runner()->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay1s);
+  Mock::VerifyAndClearExpectations(&throttler);
+  // Expect throttled wake up.
+  LazyNow lazy_now(mock_tick_clock());
+  EXPECT_EQ(
+      delay10s,
+      sequence_manager()->GetRealTimeDomain()->DelayTillNextTask(&lazy_now));
+
+  queue->ResetThrottler();
+  // Next wake up should be back to normal.
+  EXPECT_EQ(delay1s, sequence_manager()->GetRealTimeDomain()->DelayTillNextTask(
+                         &lazy_now));
+
+  // Tidy up.
+  queue->ShutdownTaskQueue();
+}
+
+TEST_P(SequenceManagerTest, TaskQueueThrottler_DelayedTaskMultipleQueues) {
   auto queues = CreateTaskQueues(2u);
 
-  MockTaskQueueObserver observer0;
-  MockTaskQueueObserver observer1;
-  queues[0]->SetObserver(&observer0);
-  queues[1]->SetObserver(&observer1);
+  StrictMock<MockTaskQueueThrottler> throttler0(queues[0].get());
+  StrictMock<MockTaskQueueThrottler> throttler1(queues[1].get());
+  queues[0]->SetThrottler(&throttler0);
+  queues[1]->SetThrottler(&throttler1);
 
   TimeTicks start_time = sequence_manager()->NowTicks();
   TimeDelta delay1s(TimeDelta::FromSeconds(1));
   TimeDelta delay10s(TimeDelta::FromSeconds(10));
 
-  EXPECT_CALL(observer0, OnQueueNextWakeUpChanged(start_time + delay1s))
+  EXPECT_CALL(throttler0,
+              GetNextAllowedWakeUp_DesiredWakeUpTime(start_time + delay1s))
       .Times(1);
-  EXPECT_CALL(observer1, OnQueueNextWakeUpChanged(start_time + delay10s))
+  EXPECT_CALL(throttler1,
+              GetNextAllowedWakeUp_DesiredWakeUpTime(start_time + delay10s))
       .Times(1);
   queues[0]->task_runner()->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
                                             delay1s);
   queues[1]->task_runner()->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
                                             delay10s);
-  testing::Mock::VerifyAndClearExpectations(&observer0);
-  testing::Mock::VerifyAndClearExpectations(&observer1);
+  testing::Mock::VerifyAndClearExpectations(&throttler0);
+  testing::Mock::VerifyAndClearExpectations(&throttler1);
 
   std::unique_ptr<TaskQueue::QueueEnabledVoter> voter0 =
       queues[0]->CreateQueueEnabledVoter();
@@ -2440,34 +2533,32 @@
       queues[1]->CreateQueueEnabledVoter();
 
   // Disabling a queue should not trigger a notification.
-  EXPECT_CALL(observer0, OnQueueNextWakeUpChanged(_)).Times(0);
   voter0->SetVoteToEnable(false);
-  Mock::VerifyAndClearExpectations(&observer0);
+  Mock::VerifyAndClearExpectations(&throttler0);
 
-  // But re-enabling it should should trigger an OnQueueNextWakeUpChanged
+  // But re-enabling it should should trigger an GetNextAllowedWakeUp
   // notification.
-  EXPECT_CALL(observer0, OnQueueNextWakeUpChanged(start_time + delay1s));
+  EXPECT_CALL(throttler0,
+              GetNextAllowedWakeUp_DesiredWakeUpTime(start_time + delay1s));
   voter0->SetVoteToEnable(true);
-  Mock::VerifyAndClearExpectations(&observer0);
+  Mock::VerifyAndClearExpectations(&throttler0);
 
   // Disabling a queue should not trigger a notification.
-  EXPECT_CALL(observer1, OnQueueNextWakeUpChanged(_)).Times(0);
   voter1->SetVoteToEnable(false);
-  Mock::VerifyAndClearExpectations(&observer0);
+  Mock::VerifyAndClearExpectations(&throttler0);
 
   // But re-enabling it should should trigger a notification.
-  EXPECT_CALL(observer1, OnQueueNextWakeUpChanged(start_time + delay10s));
+  EXPECT_CALL(throttler1,
+              GetNextAllowedWakeUp_DesiredWakeUpTime(start_time + delay10s));
   voter1->SetVoteToEnable(true);
-  Mock::VerifyAndClearExpectations(&observer1);
+  Mock::VerifyAndClearExpectations(&throttler1);
 
   // Tidy up.
-  EXPECT_CALL(observer0, OnQueueNextWakeUpChanged(_)).Times(AnyNumber());
-  EXPECT_CALL(observer1, OnQueueNextWakeUpChanged(_)).Times(AnyNumber());
   queues[0]->ShutdownTaskQueue();
   queues[1]->ShutdownTaskQueue();
 }
 
-TEST_P(SequenceManagerTest, TaskQueueObserver_DelayedWorkWhichCanRunNow) {
+TEST_P(SequenceManagerTest, TaskQueueThrottler_DelayedWorkWhichCanRunNow) {
   // This test checks that when delayed work becomes available
   // the notification still fires. This usually happens when time advances
   // and task becomes available in the middle of the scheduling code.
@@ -2480,14 +2571,14 @@
   TimeDelta delay1s(TimeDelta::FromSeconds(1));
   TimeDelta delay10s(TimeDelta::FromSeconds(10));
 
-  MockTaskQueueObserver observer;
-  queue->SetObserver(&observer);
+  StrictMock<MockTaskQueueThrottler> throttler(queue.get());
+  queue->SetThrottler(&throttler);
 
-  // We should get a notification when a delayed task is posted on an empty
-  // queue.
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_));
+  // GetNextAllowedWakeUp should be called when a delayed task is posted on an
+  // empty queue.
+  EXPECT_CALL(throttler, GetNextAllowedWakeUp_DesiredWakeUpTime(_));
   queue->task_runner()->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay1s);
-  Mock::VerifyAndClearExpectations(&observer);
+  Mock::VerifyAndClearExpectations(&throttler);
 
   std::unique_ptr<TimeDomain> mock_time_domain =
       std::make_unique<internal::RealTimeDomain>();
@@ -2495,9 +2586,9 @@
 
   AdvanceMockTickClock(delay10s);
 
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_));
+  EXPECT_CALL(throttler, GetNextAllowedWakeUp_DesiredWakeUpTime(_));
   queue->SetTimeDomain(mock_time_domain.get());
-  Mock::VerifyAndClearExpectations(&observer);
+  Mock::VerifyAndClearExpectations(&throttler);
 
   // Tidy up.
   queue->ShutdownTaskQueue();
@@ -2540,17 +2631,19 @@
 
 }  // namespace
 
-TEST_P(SequenceManagerTest, TaskQueueObserver_SweepCanceledDelayedTasks) {
+TEST_P(SequenceManagerTest, TaskQueueThrottler_SweepCanceledDelayedTasks) {
   auto queue = CreateTaskQueue();
 
-  MockTaskQueueObserver observer;
-  queue->SetObserver(&observer);
+  StrictMock<MockTaskQueueThrottler> throttler(queue.get());
+  queue->SetThrottler(&throttler);
 
   TimeTicks start_time = sequence_manager()->NowTicks();
   TimeDelta delay1(TimeDelta::FromSeconds(5));
   TimeDelta delay2(TimeDelta::FromSeconds(10));
 
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(start_time + delay1)).Times(1);
+  EXPECT_CALL(throttler,
+              GetNextAllowedWakeUp_DesiredWakeUpTime(start_time + delay1))
+      .Times(2);
 
   CancelableTask task1(mock_tick_clock());
   CancelableTask task2(mock_tick_clock());
@@ -2569,7 +2662,9 @@
   task1.weak_factory_.InvalidateWeakPtrs();
 
   // Sweeping away canceled delayed tasks should trigger a notification.
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(start_time + delay2)).Times(1);
+  EXPECT_CALL(throttler,
+              GetNextAllowedWakeUp_DesiredWakeUpTime(start_time + delay2))
+      .Times(1);
   sequence_manager()->ReclaimMemory();
 }
 
@@ -3637,55 +3732,58 @@
   }
 }
 
-TEST_P(SequenceManagerTest, GetNextScheduledWakeUp) {
+TEST_P(SequenceManagerTest, GetNextDesiredWakeUp) {
   auto queue = CreateTaskQueue();
 
-  EXPECT_EQ(absl::nullopt, queue->GetNextScheduledWakeUp());
+  EXPECT_EQ(absl::nullopt, queue->GetNextDesiredWakeUp());
 
   TimeTicks start_time = sequence_manager()->NowTicks();
   TimeDelta delay1 = TimeDelta::FromMilliseconds(10);
   TimeDelta delay2 = TimeDelta::FromMilliseconds(2);
 
   queue->task_runner()->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay1);
-  EXPECT_EQ(start_time + delay1, queue->GetNextScheduledWakeUp());
+  EXPECT_EQ(start_time + delay1, queue->GetNextDesiredWakeUp()->time);
 
   queue->task_runner()->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay2);
-  EXPECT_EQ(start_time + delay2, queue->GetNextScheduledWakeUp());
+  EXPECT_EQ(start_time + delay2, queue->GetNextDesiredWakeUp()->time);
 
   // We don't have wake-ups scheduled for disabled queues.
   std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
       queue->CreateQueueEnabledVoter();
   voter->SetVoteToEnable(false);
-  EXPECT_EQ(absl::nullopt, queue->GetNextScheduledWakeUp());
+  EXPECT_EQ(absl::nullopt, queue->GetNextDesiredWakeUp());
 
   voter->SetVoteToEnable(true);
-  EXPECT_EQ(start_time + delay2, queue->GetNextScheduledWakeUp());
+  EXPECT_EQ(start_time + delay2, queue->GetNextDesiredWakeUp()->time);
 
   // Immediate tasks shouldn't make any difference.
   queue->task_runner()->PostTask(FROM_HERE, BindOnce(&NopTask));
-  EXPECT_EQ(start_time + delay2, queue->GetNextScheduledWakeUp());
+  EXPECT_EQ(start_time + delay2, queue->GetNextDesiredWakeUp()->time);
 
   // Neither should fences.
   queue->InsertFence(TaskQueue::InsertFencePosition::kBeginningOfTime);
-  EXPECT_EQ(start_time + delay2, queue->GetNextScheduledWakeUp());
+  EXPECT_EQ(start_time + delay2, queue->GetNextDesiredWakeUp()->time);
 }
 
 TEST_P(SequenceManagerTest, SetTimeDomainForDisabledQueue) {
   auto queue = CreateTaskQueue();
 
-  MockTaskQueueObserver observer;
-  queue->SetObserver(&observer);
+  TimeTicks start_time = sequence_manager()->NowTicks();
+  TimeDelta delay(TimeDelta::FromMilliseconds(1));
 
-  queue->task_runner()->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
-                                        TimeDelta::FromMilliseconds(1));
+  StrictMock<MockTaskQueueThrottler> throttler(queue.get());
+  queue->SetThrottler(&throttler);
+
+  EXPECT_CALL(throttler,
+              GetNextAllowedWakeUp_DesiredWakeUpTime(start_time + delay));
+  queue->task_runner()->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay);
+  Mock::VerifyAndClearExpectations(&throttler);
 
   std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
       queue->CreateQueueEnabledVoter();
   voter->SetVoteToEnable(false);
 
   // We should not get a notification for a disabled queue.
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_)).Times(0);
-
   std::unique_ptr<MockTimeDomain> domain =
       std::make_unique<MockTimeDomain>(sequence_manager()->NowTicks());
   sequence_manager()->RegisterTimeDomain(domain.get());
@@ -3803,11 +3901,11 @@
 TEST_P(SequenceManagerTest, ObserverNotFiredAfterTaskQueueDestructed) {
   scoped_refptr<TestTaskQueue> main_tq = CreateTaskQueue();
 
-  MockTaskQueueObserver observer;
-  main_tq->SetObserver(&observer);
+  StrictMock<MockTaskQueueThrottler> throttler(main_tq.get());
+  main_tq->SetThrottler(&throttler);
 
-  // We don't expect the observer to fire if the TaskQueue gets destructed.
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_)).Times(0);
+  // We don't expect the throttler to be notified if the TaskQueue gets
+  // destructed.
   auto task_runner = main_tq->task_runner();
   main_tq = nullptr;
   task_runner->PostTask(FROM_HERE, BindOnce(&NopTask));
@@ -3820,24 +3918,21 @@
   scoped_refptr<TestTaskQueue> main_tq = CreateTaskQueue();
   auto task_runner = main_tq->task_runner();
 
-  MockTaskQueueObserver observer;
-  main_tq->SetObserver(&observer);
+  StrictMock<MockTaskQueueThrottler> throttler(main_tq.get());
+  main_tq->SetThrottler(&throttler);
 
   std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
       main_tq->CreateQueueEnabledVoter();
   voter->SetVoteToEnable(false);
 
-  // We don't expect the OnQueueNextWakeUpChanged to fire if the TaskQueue gets
+  // We don't expect the OnHasImmediateTask to be called if the TaskQueue gets
   // disabled.
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_)).Times(0);
-
-  // Should not fire the observer.
   task_runner->PostTask(FROM_HERE, BindOnce(&NopTask));
 
   FastForwardUntilNoTasksRemain();
   // When |voter| goes out of scope the queue will become enabled and the
   // observer will fire. We're not interested in testing that however.
-  Mock::VerifyAndClearExpectations(&observer);
+  Mock::VerifyAndClearExpectations(&throttler);
 }
 
 TEST_P(SequenceManagerTest,
@@ -3845,16 +3940,15 @@
   scoped_refptr<TestTaskQueue> main_tq = CreateTaskQueue();
   auto task_runner = main_tq->task_runner();
 
-  MockTaskQueueObserver observer;
-  main_tq->SetObserver(&observer);
+  StrictMock<MockTaskQueueThrottler> throttler(main_tq.get());
+  main_tq->SetThrottler(&throttler);
 
   std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
       main_tq->CreateQueueEnabledVoter();
   voter->SetVoteToEnable(false);
 
-  // We don't expect the observer to fire if the TaskQueue gets blocked.
-  EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_)).Times(0);
-
+  // We don't expect OnHasImmediateTask to be called if the TaskQueue gets
+  // blocked.
   WaitableEvent done_event;
   Thread thread("TestThread");
   thread.Start();
@@ -3870,7 +3964,7 @@
   FastForwardUntilNoTasksRemain();
   // When |voter| goes out of scope the queue will become enabled and the
   // observer will fire. We're not interested in testing that however.
-  Mock::VerifyAndClearExpectations(&observer);
+  Mock::VerifyAndClearExpectations(&throttler);
 }
 
 TEST_P(SequenceManagerTest, GracefulShutdown) {
diff --git a/base/task/sequence_manager/task_queue.cc b/base/task/sequence_manager/task_queue.cc
index a4b8170..8fafee7f 100644
--- a/base/task/sequence_manager/task_queue.cc
+++ b/base/task/sequence_manager/task_queue.cc
@@ -143,7 +143,7 @@
 
   // If we've not been unregistered then this must occur on the main thread.
   DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
-  impl_->SetObserver(nullptr);
+  impl_->ResetThrottler();
   impl_->sequence_manager()->ShutdownTaskQueueGracefully(TakeTaskQueueImpl());
 }
 
@@ -235,11 +235,18 @@
   return impl_->HasTaskToRunImmediatelyOrReadyDelayedTask();
 }
 
-absl::optional<TimeTicks> TaskQueue::GetNextScheduledWakeUp() {
+absl::optional<DelayedWakeUp> TaskQueue::GetNextDesiredWakeUp() {
   DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
   if (!impl_)
     return absl::nullopt;
-  return impl_->GetNextScheduledWakeUp();
+  return impl_->GetNextDesiredWakeUp();
+}
+
+void TaskQueue::UpdateDelayedWakeUp(LazyNow* lazy_now) {
+  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
+  if (!impl_)
+    return;
+  impl_->UpdateDelayedWakeUp(lazy_now);
 }
 
 void TaskQueue::SetQueuePriority(TaskQueue::QueuePriority priority) {
@@ -332,14 +339,21 @@
   dict.Add("name", name_);
 }
 
-void TaskQueue::SetObserver(Observer* observer) {
+void TaskQueue::SetThrottler(Throttler* throttler) {
   DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
   if (!impl_)
     return;
 
-  // Observer is guaranteed to outlive TaskQueue and TaskQueueImpl lifecycle is
-  // controlled by |this|.
-  impl_->SetObserver(observer);
+  // |throttler| is guaranteed to outlive TaskQueue and TaskQueueImpl lifecycle
+  // is controlled by |this|.
+  impl_->SetThrottler(throttler);
+}
+
+void TaskQueue::ResetThrottler() {
+  DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
+  if (!impl_)
+    return;
+  impl_->ResetThrottler();
 }
 
 void TaskQueue::SetShouldReportPostedTasksWhenDisabled(bool should_report) {
diff --git a/base/task/sequence_manager/task_queue.h b/base/task/sequence_manager/task_queue.h
index c9d57d2..75c85e7 100644
--- a/base/task/sequence_manager/task_queue.h
+++ b/base/task/sequence_manager/task_queue.h
@@ -46,20 +46,47 @@
 // task queue always gets unregistered on the main thread.
 class BASE_EXPORT TaskQueue : public RefCountedThreadSafe<TaskQueue> {
  public:
-  class Observer {
+  // Interface that lets a task queue be throttled by changing the wake up time
+  // and optionally, by inserting fences. A wake up in this context is a
+  // notification at a given time that lets this TaskQueue know of newly ripe
+  // delayed tasks if it's enabled. By delaying the desired wake up time to a
+  // different allowed wake up time, the Throttler can hold off delayed tasks
+  // that would otherwise by allowed to run sooner.
+  class BASE_EXPORT Throttler {
    public:
-    virtual ~Observer() = default;
+    // Invoked when the TaskQueue's next allowed wake up time is reached and is
+    // enabled, even if blocked by a fence. That wake up is defined by the last
+    // value returned from GetNextAllowedWakeUp().
+    // This is always called on the thread this TaskQueue is associated with.
+    virtual void OnWakeUp(LazyNow* lazy_now) = 0;
 
-    // Notify observer that the time at which this queue wants to run
-    // the next task has changed. |next_wakeup| can be in the past
-    // (e.g. TimeTicks() can be used to notify about immediate work).
-    // Can be called on any thread
-    // All methods but SetObserver, SetTimeDomain and GetTimeDomain can be
-    // called on |queue|.
-    //
-    // TODO(altimin): Make it absl::optional<TimeTicks> to tell
-    // observer about cancellations.
-    virtual void OnQueueNextWakeUpChanged(TimeTicks next_wake_up) = 0;
+    // Invoked when the TaskQueue newly gets a pending immediate task and is
+    // enabled, even if blocked by a fence. Redundant calls are possible when
+    // the TaskQueue already had a pending immediate task.
+    // The implementation may use this to:
+    // - Restrict task execution by inserting/updating a fence.
+    // - Update the TaskQueue's next delayed wake up via UpdateDelayedWakeUp().
+    //   This allows the Throttler to perform additional operations later from
+    //   OnWakeUp().
+    // This is always called on the thread this TaskQueue is associated with.
+    virtual void OnHasImmediateTask() = 0;
+
+    // Invoked when the TaskQueue is enabled and wants to know when to schedule
+    // the next delayed wake-up (which happens at least every time this queue is
+    // about to cause the next wake up) provided |next_desired_wake_up|, the
+    // wake-up for the next pending delayed task in this queue (pending delayed
+    // tasks that are ripe may be ignored), or nullopt if there's no pending
+    // delayed task. |has_ready_task| indicates whether there are immediate
+    // tasks or ripe delayed tasks. The implementation should return the next
+    // allowed wake up, or nullopt if no future wake-up is necessary.
+    // This is always called on the thread this TaskQueue is associated with.
+    virtual absl::optional<DelayedWakeUp> GetNextAllowedWakeUp(
+        LazyNow* lazy_now,
+        absl::optional<DelayedWakeUp> next_desired_wake_up,
+        bool has_ready_task) = 0;
+
+   protected:
+    ~Throttler() = default;
   };
 
   // Shuts down the queue. All tasks currently queued will be discarded.
@@ -243,13 +270,15 @@
   // Returns true iff this queue has immediate tasks or delayed tasks that are
   // ripe for execution. Ignores the queue's enabled state and fences.
   // NOTE: this must be called on the thread this TaskQueue was created by.
+  // TODO(etiennep): Rename to HasReadyTask() and add LazyNow parameter.
   bool HasTaskToRunImmediatelyOrReadyDelayedTask() const;
 
-  // Returns requested run time of next scheduled wake-up for a delayed task
-  // which is not ready to run. If there are no such tasks (immediate tasks
-  // don't count) or the queue is disabled it returns nullopt.
+  // Returns a wake-up for the next pending delayed task (pending delayed tasks
+  // that are ripe may be ignored), ignoring Throttler is any. If there are no
+  // such tasks (immediate tasks don't count) or the queue is disabled it
+  // returns nullopt.
   // NOTE: this must be called on the thread this TaskQueue was created by.
-  absl::optional<TimeTicks> GetNextScheduledWakeUp();
+  absl::optional<DelayedWakeUp> GetNextDesiredWakeUp();
 
   // Can be called on any thread.
   virtual const char* GetName() const;
@@ -322,7 +351,17 @@
   // Returns true if the queue has a fence which is blocking execution of tasks.
   bool BlockedByFence() const;
 
-  void SetObserver(Observer* observer);
+  // Associates |throttler| to this queue. Only one throttler can be associated
+  // with this queue. |throttler| must outlive this TaskQueue, or remain valid
+  // until ResetThrottler().
+  void SetThrottler(Throttler* throttler);
+  // Disassociates the current throttler from this queue, if any.
+  void ResetThrottler();
+
+  // Updates the task queue's next wake up time in its time domain, taking into
+  // account the desired run time of queued tasks and policies enforced by the
+  // throttler if any.
+  void UpdateDelayedWakeUp(LazyNow* lazy_now);
 
   // Controls whether or not the queue will emit traces events when tasks are
   // posted to it while disabled. This only applies for the current or next
diff --git a/base/task/sequence_manager/task_queue_impl.cc b/base/task/sequence_manager/task_queue_impl.cc
index 741dc8f..058c345 100644
--- a/base/task/sequence_manager/task_queue_impl.cc
+++ b/base/task/sequence_manager/task_queue_impl.cc
@@ -179,7 +179,6 @@
     any_thread_.unregistered = true;
     any_thread_.time_domain = nullptr;
     immediate_incoming_queue.swap(any_thread_.immediate_incoming_queue);
-    any_thread_.task_queue_observer = nullptr;
   }
 
   if (main_thread_only().time_domain)
@@ -187,7 +186,7 @@
 
   main_thread_only().on_task_completed_handler = OnTaskCompletedHandler();
   main_thread_only().time_domain = nullptr;
-  main_thread_only().task_queue_observer = nullptr;
+  main_thread_only().throttler = nullptr;
   empty_queues_to_reload_handle_.ReleaseAtomicFlag();
 
   // It is possible for a task to hold a scoped_refptr to this, which
@@ -449,9 +448,8 @@
   DCHECK(main_thread_only().immediate_work_queue->Empty());
   main_thread_only().immediate_work_queue->TakeImmediateIncomingQueueTasks();
 
-  if (main_thread_only().task_queue_observer && IsQueueEnabled()) {
-    main_thread_only().task_queue_observer->OnQueueNextWakeUpChanged(
-        TimeTicks());
+  if (main_thread_only().throttler && IsQueueEnabled()) {
+    main_thread_only().throttler->OnHasImmediateTask();
   }
 }
 
@@ -533,7 +531,7 @@
   return !any_thread_.immediate_incoming_queue.empty();
 }
 
-absl::optional<DelayedWakeUp> TaskQueueImpl::GetNextScheduledWakeUpImpl() {
+absl::optional<DelayedWakeUp> TaskQueueImpl::GetNextDesiredWakeUp() {
   // Note we don't scheduled a wake-up for disabled queues.
   if (main_thread_only().delayed_incoming_queue.empty() || !IsQueueEnabled())
     return absl::nullopt;
@@ -551,11 +549,11 @@
   return DelayedWakeUp{top_task.delayed_run_time, resolution};
 }
 
-absl::optional<TimeTicks> TaskQueueImpl::GetNextScheduledWakeUp() {
-  absl::optional<DelayedWakeUp> wake_up = GetNextScheduledWakeUpImpl();
-  if (!wake_up)
-    return absl::nullopt;
-  return wake_up->time;
+void TaskQueueImpl::OnWakeUp(LazyNow* lazy_now) {
+  MoveReadyDelayedTasksToWorkQueue(lazy_now);
+  if (main_thread_only().throttler) {
+    main_thread_only().throttler->OnWakeUp(lazy_now);
+  }
 }
 
 void TaskQueueImpl::MoveReadyDelayedTasksToWorkQueue(LazyNow* lazy_now) {
@@ -957,6 +955,8 @@
   }
 
   LazyNow lazy_now = main_thread_only().time_domain->CreateLazyNow();
+  // If there is a throttler, it will be notified of pending delayed and
+  // immediate tasks inside UpdateDelayedWakeUp().
   UpdateDelayedWakeUp(&lazy_now);
 
   bool has_pending_immediate_work = false;
@@ -979,12 +979,6 @@
 
   // Finally, enable or disable the queue with the selector.
   if (enabled) {
-    if (has_pending_immediate_work && main_thread_only().task_queue_observer) {
-      // Delayed work notification will be issued via time domain.
-      main_thread_only().task_queue_observer->OnQueueNextWakeUpChanged(
-          TimeTicks());
-    }
-
     // Note the selector calls SequenceManager::OnTaskQueueEnabled which posts
     // a DoWork if needed.
     sequence_manager_->main_thread_only().selector.EnableQueue(this);
@@ -1024,14 +1018,14 @@
   any_thread_.immediate_work_queue_empty =
       main_thread_only().immediate_work_queue->Empty();
 
-  if (main_thread_only().task_queue_observer) {
-    // If there's an observer we need a DoWork for the callback to be issued by
-    // ReloadEmptyImmediateWorkQueue. The callback isn't sent for disabled
-    // queues.
+  if (main_thread_only().throttler) {
+    // If there's a Throttler, always ScheduleWork() when immediate work is
+    // posted and the queue is enabled, to ensure that
+    // Throttler::OnHasImmediateTask() is invoked.
     any_thread_.post_immediate_task_should_schedule_work = IsQueueEnabled();
   } else {
-    // Otherwise we need PostImmediateTaskImpl to ScheduleWork unless the queue
-    // is blocked or disabled.
+    // Otherwise, ScheduleWork() only if the queue is enabled and there isn't a
+    // fence to prevent the task from being executed.
     any_thread_.post_immediate_task_should_schedule_work =
         IsQueueEnabled() && !main_thread_only().current_fence;
   }
@@ -1103,46 +1097,44 @@
   }
 }
 
-void TaskQueueImpl::SetObserver(TaskQueue::Observer* observer) {
-  if (observer) {
-    DCHECK(!main_thread_only().task_queue_observer)
-        << "Can't assign two different observers to "
-           "base::sequence_manager:TaskQueue";
-  }
+void TaskQueueImpl::SetThrottler(TaskQueue::Throttler* throttler) {
+  DCHECK(throttler);
+  DCHECK(!main_thread_only().throttler)
+      << "Can't assign two different throttlers to "
+         "base::sequence_manager:TaskQueue";
 
-  main_thread_only().task_queue_observer = observer;
+  main_thread_only().throttler = throttler;
+}
 
-  base::internal::CheckedAutoLock lock(any_thread_lock_);
-  any_thread_.task_queue_observer = observer;
+void TaskQueueImpl::ResetThrottler() {
+  main_thread_only().throttler = nullptr;
+  LazyNow lazy_now = main_thread_only().time_domain->CreateLazyNow();
+  // The current delayed wake up may have been determined by the Throttler.
+  // Update it now that there is no Throttler.
+  UpdateDelayedWakeUp(&lazy_now);
 }
 
 void TaskQueueImpl::UpdateDelayedWakeUp(LazyNow* lazy_now) {
-  return UpdateDelayedWakeUpImpl(lazy_now, GetNextScheduledWakeUpImpl());
+  absl::optional<DelayedWakeUp> wake_up = GetNextDesiredWakeUp();
+  if (main_thread_only().throttler && IsQueueEnabled()) {
+    // GetNextAllowedWakeUp() may return a non-null wake_up even if |wake_up| is
+    // nullopt, e.g. to throttle immediate tasks.
+    wake_up = main_thread_only().throttler->GetNextAllowedWakeUp(
+        lazy_now, wake_up, HasTaskToRunImmediatelyOrReadyDelayedTask());
+  }
+  SetNextDelayedWakeUp(lazy_now, wake_up);
 }
 
-void TaskQueueImpl::UpdateDelayedWakeUpImpl(
+void TaskQueueImpl::SetNextDelayedWakeUp(
     LazyNow* lazy_now,
     absl::optional<DelayedWakeUp> wake_up) {
   if (main_thread_only().scheduled_wake_up == wake_up)
     return;
   main_thread_only().scheduled_wake_up = wake_up;
-
-  if (wake_up && main_thread_only().task_queue_observer &&
-      !HasTaskToRunImmediately()) {
-    main_thread_only().task_queue_observer->OnQueueNextWakeUpChanged(
-        wake_up->time);
-  }
-
   main_thread_only().time_domain->SetNextWakeUpForQueue(this, wake_up,
                                                         lazy_now);
 }
 
-void TaskQueueImpl::SetDelayedWakeUpForTesting(
-    absl::optional<DelayedWakeUp> wake_up) {
-  LazyNow lazy_now = main_thread_only().time_domain->CreateLazyNow();
-  UpdateDelayedWakeUpImpl(&lazy_now, wake_up);
-}
-
 bool TaskQueueImpl::HasTaskToRunImmediately() const {
   // Any work queue tasks count as immediate work.
   if (!main_thread_only().delayed_work_queue->Empty() ||
diff --git a/base/task/sequence_manager/task_queue_impl.h b/base/task/sequence_manager/task_queue_impl.h
index 4da4798..41082a2 100644
--- a/base/task/sequence_manager/task_queue_impl.h
+++ b/base/task/sequence_manager/task_queue_impl.h
@@ -112,7 +112,7 @@
   bool IsEmpty() const;
   size_t GetNumberOfPendingTasks() const;
   bool HasTaskToRunImmediatelyOrReadyDelayedTask() const;
-  absl::optional<TimeTicks> GetNextScheduledWakeUp();
+  absl::optional<DelayedWakeUp> GetNextDesiredWakeUp();
   void SetQueuePriority(TaskQueue::QueuePriority priority);
   TaskQueue::QueuePriority GetQueuePriority() const;
   void AddTaskObserver(TaskObserver* task_observer);
@@ -125,9 +125,8 @@
   void RemoveFence();
   bool HasActiveFence();
   bool BlockedByFence() const;
-
-  // Implementation of TaskQueue::SetObserver.
-  void SetObserver(TaskQueue::Observer* observer);
+  void SetThrottler(TaskQueue::Throttler* throttler);
+  void ResetThrottler();
 
   void UnregisterTaskQueue();
 
@@ -187,6 +186,8 @@
   // |delayed_work_queue|. Must be called from the main thread.
   void MoveReadyDelayedTasksToWorkQueue(LazyNow* lazy_now);
 
+  void OnWakeUp(LazyNow* lazy_now);
+
   base::internal::HeapHandle heap_handle() const {
     return main_thread_only().heap_handle;
   }
@@ -238,8 +239,15 @@
   // and this queue can be safely deleted on any thread.
   bool IsUnregistered() const;
 
+  // Updates this queue's next wake up time in the time domain,
+  // taking into account the desired run time of queued tasks and
+  // policies enforced by the Throttler.
+  void UpdateDelayedWakeUp(LazyNow* lazy_now);
+
  protected:
-  void SetDelayedWakeUpForTesting(absl::optional<DelayedWakeUp> wake_up);
+  // Sets this queue's next wake up time to |wake_up| in the time domain.
+  void SetNextDelayedWakeUp(LazyNow* lazy_now,
+                            absl::optional<DelayedWakeUp> wake_up);
 
  private:
   friend class WorkQueue;
@@ -351,7 +359,7 @@
     // See description inside struct AnyThread for details.
     TimeDomain* time_domain;
 
-    TaskQueue::Observer* task_queue_observer = nullptr;
+    TaskQueue::Throttler* throttler = nullptr;
 
     std::unique_ptr<WorkQueue> delayed_work_queue;
     std::unique_ptr<WorkQueue> immediate_work_queue;
@@ -415,8 +423,6 @@
   // threads.
   void PushOntoDelayedIncomingQueue(Task pending_task);
 
-  absl::optional<DelayedWakeUp> GetNextScheduledWakeUpImpl();
-
   void ScheduleDelayedWorkTask(Task pending_task);
 
   void MoveReadyImmediateTasksToImmediateWorkQueueLocked()
@@ -436,11 +442,6 @@
   static Value QueueAsValue(const TaskDeque& queue, TimeTicks now);
   static Value TaskAsValue(const Task& task, TimeTicks now);
 
-  // Schedules delayed work on time domain and calls the observer.
-  void UpdateDelayedWakeUp(LazyNow* lazy_now);
-  void UpdateDelayedWakeUpImpl(LazyNow* lazy_now,
-                               absl::optional<DelayedWakeUp> wake_up);
-
   // Activate a delayed fence if a time has come.
   void ActivateDelayedFenceIfNeeded(TimeTicks now);
 
@@ -499,8 +500,6 @@
     // locked before accessing from other threads.
     TimeDomain* time_domain;
 
-    TaskQueue::Observer* task_queue_observer = nullptr;
-
     TaskDeque immediate_incoming_queue;
 
     // True if main_thread_only().immediate_work_queue is empty.
diff --git a/base/task/sequence_manager/time_domain.cc b/base/task/sequence_manager/time_domain.cc
index 80f62f2..32bbcdf 100644
--- a/base/task/sequence_manager/time_domain.cc
+++ b/base/task/sequence_manager/time_domain.cc
@@ -121,13 +121,33 @@
 
 void TimeDomain::MoveReadyDelayedTasksToWorkQueues(LazyNow* lazy_now) {
   DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
-  // Wake up any queues with pending delayed work.  Note std::multimap stores
-  // the elements sorted by key, so the begin() iterator points to the earliest
-  // queue to wake-up.
+  // Wake up any queues with pending delayed work.
+  bool update_needed = false;
   while (!delayed_wake_up_queue_.empty() &&
          delayed_wake_up_queue_.Min().wake_up.time <= lazy_now->Now()) {
     internal::TaskQueueImpl* queue = delayed_wake_up_queue_.Min().queue;
-    queue->MoveReadyDelayedTasksToWorkQueue(lazy_now);
+    // OnWakeUp() is expected to update the next wake-up for this queue with
+    // SetNextWakeUpForQueue(), thus allowing us to make progress.
+    queue->OnWakeUp(lazy_now);
+    update_needed = true;
+  }
+  if (!update_needed || delayed_wake_up_queue_.empty())
+    return;
+  // If any queue was notified, possibly update following queues. This ensures
+  // the wake up is up to date, which is necessary because calling OnWakeUp() on
+  // a throttled queue may affect state that is shared between other related
+  // throttled queues. The wake up for an affected queue might be pushed back
+  // and needs to be updated. This is done lazily only once the related queue
+  // becomes the next one to wake up, since that wake up can't be moved up.
+  // |delayed_wake_up_queue_| is non-empty here, per the condition above.
+  internal::TaskQueueImpl* queue = delayed_wake_up_queue_.Min().queue;
+  queue->UpdateDelayedWakeUp(lazy_now);
+  while (!delayed_wake_up_queue_.empty()) {
+    internal::TaskQueueImpl* old_queue =
+        std::exchange(queue, delayed_wake_up_queue_.Min().queue);
+    if (old_queue == queue)
+      break;
+    queue->UpdateDelayedWakeUp(lazy_now);
   }
 }
 
diff --git a/base/task/sequence_manager/time_domain_unittest.cc b/base/task/sequence_manager/time_domain_unittest.cc
index 09488e9c..4854ec4 100644
--- a/base/task/sequence_manager/time_domain_unittest.cc
+++ b/base/task/sequence_manager/time_domain_unittest.cc
@@ -32,7 +32,7 @@
       : TaskQueueImpl(sequence_manager, time_domain, spec) {}
   ~TaskQueueImplForTest() {}
 
-  using TaskQueueImpl::SetDelayedWakeUpForTesting;
+  using TaskQueueImpl::SetNextDelayedWakeUp;
 };
 
 class TestTimeDomain : public TimeDomain {
@@ -104,7 +104,7 @@
   EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, delayed_runtime));
   TimeTicks now = time_domain_->Now();
   LazyNow lazy_now(now);
-  task_queue_->SetDelayedWakeUpForTesting(DelayedWakeUp{now + delay});
+  task_queue_->SetNextDelayedWakeUp(&lazy_now, DelayedWakeUp{now + delay});
 
   EXPECT_FALSE(time_domain_->empty());
   EXPECT_EQ(delayed_runtime, time_domain_->NextScheduledRunTime());
@@ -124,7 +124,7 @@
   EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, delayed_runtime1));
   TimeTicks now = time_domain_->Now();
   LazyNow lazy_now(now);
-  task_queue_->SetDelayedWakeUpForTesting(DelayedWakeUp{delayed_runtime1});
+  task_queue_->SetNextDelayedWakeUp(&lazy_now, DelayedWakeUp{delayed_runtime1});
 
   EXPECT_EQ(delayed_runtime1, time_domain_->NextScheduledRunTime());
 
@@ -133,7 +133,7 @@
   // Now schedule a later wake_up, which should replace the previously
   // requested one.
   EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, delayed_runtime2));
-  task_queue_->SetDelayedWakeUpForTesting(DelayedWakeUp{delayed_runtime2});
+  task_queue_->SetNextDelayedWakeUp(&lazy_now, DelayedWakeUp{delayed_runtime2});
 
   EXPECT_EQ(delayed_runtime2, time_domain_->NextScheduledRunTime());
   Mock::VerifyAndClearExpectations(time_domain_.get());
@@ -165,19 +165,19 @@
   TimeTicks now = time_domain_->Now();
   LazyNow lazy_now(now);
   EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, now + delay1));
-  task_queue_->SetDelayedWakeUpForTesting(DelayedWakeUp{now + delay1});
+  task_queue_->SetNextDelayedWakeUp(&lazy_now, DelayedWakeUp{now + delay1});
 
   Mock::VerifyAndClearExpectations(time_domain_.get());
 
   // SetNextDelayedDoWork should not be called when scheduling later tasks.
   EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, _)).Times(0);
-  task_queue2->SetDelayedWakeUpForTesting(DelayedWakeUp{now + delay2});
-  task_queue3->SetDelayedWakeUpForTesting(DelayedWakeUp{now + delay3});
+  task_queue2->SetNextDelayedWakeUp(&lazy_now, DelayedWakeUp{now + delay2});
+  task_queue3->SetNextDelayedWakeUp(&lazy_now, DelayedWakeUp{now + delay3});
 
   // SetNextDelayedDoWork should be called when scheduling earlier tasks.
   Mock::VerifyAndClearExpectations(time_domain_.get());
   EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, now + delay4));
-  task_queue4->SetDelayedWakeUpForTesting(DelayedWakeUp{now + delay4});
+  task_queue4->SetNextDelayedWakeUp(&lazy_now, DelayedWakeUp{now + delay4});
 
   Mock::VerifyAndClearExpectations(time_domain_.get());
 
@@ -197,9 +197,9 @@
   LazyNow lazy_now(now);
   TimeTicks wake_up1 = now + TimeDelta::FromMilliseconds(10);
   EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, wake_up1)).Times(1);
-  task_queue_->SetDelayedWakeUpForTesting(DelayedWakeUp{wake_up1});
+  task_queue_->SetNextDelayedWakeUp(&lazy_now, DelayedWakeUp{wake_up1});
   TimeTicks wake_up2 = now + TimeDelta::FromMilliseconds(100);
-  task_queue2->SetDelayedWakeUpForTesting(DelayedWakeUp{wake_up2});
+  task_queue2->SetNextDelayedWakeUp(&lazy_now, DelayedWakeUp{wake_up2});
   EXPECT_FALSE(time_domain_->empty());
 
   EXPECT_EQ(task_queue_.get(), time_domain_->NextScheduledTaskQueue());
@@ -234,7 +234,8 @@
   LazyNow lazy_now_1(now);
   TimeTicks delayed_runtime = now + delay;
   EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, delayed_runtime));
-  task_queue_->SetDelayedWakeUpForTesting(DelayedWakeUp{delayed_runtime});
+  task_queue_->SetNextDelayedWakeUp(&lazy_now_1,
+                                    DelayedWakeUp{delayed_runtime});
 
   EXPECT_EQ(delayed_runtime, time_domain_->NextScheduledRunTime());
 
@@ -254,12 +255,12 @@
   TimeTicks run_time = now + TimeDelta::FromMilliseconds(20);
 
   EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, run_time));
-  task_queue_->SetDelayedWakeUpForTesting(DelayedWakeUp{run_time});
+  task_queue_->SetNextDelayedWakeUp(&lazy_now, DelayedWakeUp{run_time});
 
   EXPECT_EQ(task_queue_.get(), time_domain_->NextScheduledTaskQueue());
 
   EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, TimeTicks::Max()));
-  task_queue_->SetDelayedWakeUpForTesting(absl::nullopt);
+  task_queue_->SetNextDelayedWakeUp(&lazy_now, absl::nullopt);
   EXPECT_FALSE(time_domain_->NextScheduledTaskQueue());
 }
 
@@ -273,11 +274,11 @@
   TimeTicks run_time1 = now + TimeDelta::FromMilliseconds(20);
   TimeTicks run_time2 = now + TimeDelta::FromMilliseconds(40);
   EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, run_time1));
-  task_queue_->SetDelayedWakeUpForTesting(DelayedWakeUp{run_time1});
+  task_queue_->SetNextDelayedWakeUp(&lazy_now, DelayedWakeUp{run_time1});
   Mock::VerifyAndClearExpectations(time_domain_.get());
 
   EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, _)).Times(0);
-  task_queue2->SetDelayedWakeUpForTesting(DelayedWakeUp{run_time2});
+  task_queue2->SetNextDelayedWakeUp(&lazy_now, DelayedWakeUp{run_time2});
   Mock::VerifyAndClearExpectations(time_domain_.get());
 
   EXPECT_EQ(task_queue_.get(), time_domain_->NextScheduledTaskQueue());
@@ -285,7 +286,7 @@
   EXPECT_EQ(run_time1, time_domain_->NextScheduledRunTime());
 
   EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, run_time2));
-  task_queue_->SetDelayedWakeUpForTesting(absl::nullopt);
+  task_queue_->SetNextDelayedWakeUp(&lazy_now, absl::nullopt);
   EXPECT_EQ(task_queue2.get(), time_domain_->NextScheduledTaskQueue());
 
   EXPECT_EQ(run_time2, time_domain_->NextScheduledRunTime());
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java b/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java
index f538088f..41d0778 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java
@@ -6,10 +6,10 @@
 
 import android.content.SharedPreferences;
 
-import org.chromium.base.ObserverList;
-
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -23,8 +23,7 @@
 
     // Guarded on its own monitor.
     private final Map<String, Object> mData;
-    private final ObserverList<OnSharedPreferenceChangeListener> mObservers =
-            new ObserverList<OnSharedPreferenceChangeListener>();
+    private final List<OnSharedPreferenceChangeListener> mObservers = new ArrayList<>();
 
     public InMemorySharedPreferences() {
         mData = new HashMap<String, Object>();
@@ -107,7 +106,7 @@
             SharedPreferences.OnSharedPreferenceChangeListener
                     listener) {
         synchronized (mObservers) {
-            mObservers.addObserver(listener);
+            mObservers.add(listener);
         }
     }
 
@@ -115,7 +114,7 @@
     public void unregisterOnSharedPreferenceChangeListener(
             SharedPreferences.OnSharedPreferenceChangeListener listener) {
         synchronized (mObservers) {
-            mObservers.removeObserver(listener);
+            mObservers.remove(listener);
         }
     }
 
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
index 053602e..ce32ee7e 100644
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -141,6 +141,9 @@
 class CompletionEvent;
 class TileTaskManagerImpl;
 }
+namespace chromecast {
+class CrashUtil;
+}
 namespace chromeos {
 class BlockingMethodCaller;
 namespace system {
@@ -205,6 +208,9 @@
 namespace history_report {
 class HistoryReportJniBridge;
 }
+namespace ios_web_view {
+class WebViewBrowserState;
+}
 namespace leveldb_env {
 class DBTracker;
 }
@@ -411,6 +417,7 @@
   friend class ash::MojoUtils;  // http://crbug.com/1055467
   friend class ash::BrowserDataMigrator;
   friend class blink::DiskDataAllocator;
+  friend class chromecast::CrashUtil;
   friend class content::BrowserProcessIOThread;
   friend class content::NetworkServiceInstancePrivate;
   friend class content::PepperPrintSettingsManagerImpl;
@@ -421,6 +428,7 @@
   friend class cronet::CronetPrefsManager;
   friend class cronet::CronetURLRequestContext;
   friend class crosapi::LacrosThreadPriorityDelegate;
+  friend class ios_web_view::WebViewBrowserState;
   friend class memory_instrumentation::OSMetrics;
   friend class metrics::AndroidMetricsServiceClient;
   friend class module_installer::ScopedAllowModulePakLoad;
diff --git a/base/trace_event/common/trace_event_common.h b/base/trace_event/common/trace_event_common.h
index 41d1b86..e782544 100644
--- a/base/trace_event/common/trace_event_common.h
+++ b/base/trace_event/common/trace_event_common.h
@@ -946,6 +946,12 @@
   INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP(                   \
       TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN, category_group, name, id, \
       TRACE_EVENT_API_CURRENT_THREAD_ID, timestamp, TRACE_EVENT_FLAG_COPY)
+#define TRACE_EVENT_COPY_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1(             \
+    category_group, name, id, timestamp, arg1_name, arg1_val)              \
+  INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP(                      \
+      TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN, category_group, name, id,    \
+      TRACE_EVENT_API_CURRENT_THREAD_ID, timestamp, TRACE_EVENT_FLAG_COPY, \
+      arg1_name, arg1_val)
 #define TRACE_EVENT_COPY_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(          \
     category_group, name, id, timestamp)                              \
   INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP(                 \
diff --git a/base/trace_event/memory_infra_background_allowlist.cc b/base/trace_event/memory_infra_background_allowlist.cc
index c11fbb1..6e302ed 100644
--- a/base/trace_event/memory_infra_background_allowlist.cc
+++ b/base/trace_event/memory_infra_background_allowlist.cc
@@ -21,6 +21,7 @@
 // little processor and memory overhead.
 // TODO(ssid): Some dump providers do not create ownership edges on background
 // dump. So, the effective size will not be correct.
+// clang-format off
 const char* const kDumpProviderAllowlist[] = {
     "android::ResourceManagerImpl",
     "AutocompleteController",
@@ -77,8 +78,13 @@
 // A list of string names that are allowed for the memory allocator dumps in
 // background mode.
 const char* const kAllocatorDumpNameAllowlist[] = {
+    // Some of the blink values vary based on compile time flags. The compile
+    // timeflags are not in base, so all are listed here.
+    "blink_gc/main/allocated_objects",
     "blink_gc/main/heap",
     "blink_gc/workers/heap/worker_0x?",
+    "blink_gc/workers/worker_0x?/heap",
+    "blink_gc/workers/worker_0x?/allocated_objects",
     "blink_objects/AdSubframe",
     "blink_objects/ArrayBufferContents",
     "blink_objects/AudioHandler",
@@ -408,6 +414,7 @@
     "tracing/heap_profiler_partition_alloc/AllocationRegister",
     nullptr  // End of list marker.
 };
+// clang-format on
 
 const char* const* g_dump_provider_allowlist = kDumpProviderAllowlist;
 const char* const* g_allocator_dump_name_allowlist =
diff --git a/base/trace_event/trace_log.cc b/base/trace_event/trace_log.cc
index 73c3f9c..426b00c 100644
--- a/base/trace_event/trace_log.cc
+++ b/base/trace_event/trace_log.cc
@@ -53,6 +53,8 @@
 #include "third_party/perfetto/include/perfetto/ext/trace_processor/export_json.h"  // nogncheck
 #include "third_party/perfetto/include/perfetto/trace_processor/trace_processor_storage.h"  // nogncheck
 #include "third_party/perfetto/protos/perfetto/config/interceptor_config.gen.h"  // nogncheck
+#include "third_party/perfetto/protos/perfetto/trace/track_event/process_descriptor.gen.h"  // nogncheck
+#include "third_party/perfetto/protos/perfetto/trace/track_event/thread_descriptor.gen.h"  // nogncheck
 #endif
 
 #if defined(OS_WIN)
@@ -289,31 +291,96 @@
   perfetto::DynamicCategory category(
       TraceLog::GetInstance()->GetCategoryGroupName(
           trace_event->category_group_enabled()));
-  PERFETTO_INTERNAL_TRACK_EVENT(
-      category, trace_event->name(),
-      perfetto::protos::pbzero::TrackEvent::TYPE_UNSPECIFIED,
-      [&](perfetto::EventContext ctx) {
-        WriteDebugAnnotations(trace_event, ctx.event());
-        auto* legacy_event = ctx.event()->set_legacy_event();
-        legacy_event->set_phase(trace_event->phase());
-        uint32_t id_flags =
-            trace_event->flags() &
-            (TRACE_EVENT_FLAG_HAS_ID | TRACE_EVENT_FLAG_HAS_LOCAL_ID |
-             TRACE_EVENT_FLAG_HAS_GLOBAL_ID);
-        switch (id_flags) {
-          case TRACE_EVENT_FLAG_HAS_ID:
-            legacy_event->set_unscoped_id(trace_event->id());
-            break;
-          case TRACE_EVENT_FLAG_HAS_LOCAL_ID:
-            legacy_event->set_local_id(trace_event->id());
-            break;
-          case TRACE_EVENT_FLAG_HAS_GLOBAL_ID:
-            legacy_event->set_global_id(trace_event->id());
-            break;
-          default:
-            break;
-        }
-      });
+  auto write_args = [trace_event](perfetto::EventContext ctx) {
+    WriteDebugAnnotations(trace_event, ctx.event());
+    uint32_t id_flags = trace_event->flags() & (TRACE_EVENT_FLAG_HAS_ID |
+                                                TRACE_EVENT_FLAG_HAS_LOCAL_ID |
+                                                TRACE_EVENT_FLAG_HAS_GLOBAL_ID);
+    if (!id_flags &&
+        perfetto::internal::TrackEventLegacy::PhaseToType(
+            trace_event->phase()) !=
+            perfetto::protos::pbzero::TrackEvent::TYPE_UNSPECIFIED) {
+      return;
+    }
+    auto* legacy_event = ctx.event()->set_legacy_event();
+    legacy_event->set_phase(trace_event->phase());
+    switch (id_flags) {
+      case TRACE_EVENT_FLAG_HAS_ID:
+        legacy_event->set_unscoped_id(trace_event->id());
+        break;
+      case TRACE_EVENT_FLAG_HAS_LOCAL_ID:
+        legacy_event->set_local_id(trace_event->id());
+        break;
+      case TRACE_EVENT_FLAG_HAS_GLOBAL_ID:
+        legacy_event->set_global_id(trace_event->id());
+        break;
+      default:
+        break;
+    }
+  };
+
+  auto phase = trace_event->phase();
+  auto flags = trace_event->flags();
+  base::TimeTicks timestamp = trace_event->timestamp().is_null()
+                                  ? TRACE_TIME_TICKS_NOW()
+                                  : trace_event->timestamp();
+  if (phase == TRACE_EVENT_PHASE_COMPLETE) {
+    phase = TRACE_EVENT_PHASE_BEGIN;
+  } else if (phase == TRACE_EVENT_PHASE_INSTANT) {
+    auto scope = flags & TRACE_EVENT_FLAG_SCOPE_MASK;
+    switch (scope) {
+      case TRACE_EVENT_SCOPE_GLOBAL:
+        PERFETTO_INTERNAL_LEGACY_EVENT_ON_TRACK(
+            phase, category, trace_event->name(), ::perfetto::Track::Global(0),
+            timestamp, write_args);
+        return;
+      case TRACE_EVENT_SCOPE_PROCESS:
+        PERFETTO_INTERNAL_LEGACY_EVENT_ON_TRACK(
+            phase, category, trace_event->name(),
+            ::perfetto::ProcessTrack::Current(), timestamp, write_args);
+        return;
+      default:
+      case TRACE_EVENT_SCOPE_THREAD: /* Fallthrough. */
+        break;
+    }
+  }
+  if (trace_event->thread_id() &&
+      trace_event->thread_id() != base::PlatformThread::CurrentId()) {
+    PERFETTO_INTERNAL_LEGACY_EVENT_ON_TRACK(
+        phase, category, trace_event->name(),
+        perfetto::ThreadTrack::ForThread(trace_event->thread_id()), timestamp,
+        write_args);
+    return;
+  }
+  PERFETTO_INTERNAL_LEGACY_EVENT_ON_TRACK(
+      phase, category, trace_event->name(),
+      perfetto::internal::TrackEventInternal::kDefaultTrack, timestamp,
+      write_args);
+}
+
+void OnUpdateLegacyTraceEventDuration(
+    const unsigned char* category_group_enabled,
+    const char* name,
+    TraceEventHandle handle,
+    int thread_id,
+    bool explicit_timestamps,
+    const TimeTicks& now,
+    const ThreadTicks& thread_now,
+    ThreadInstructionCount thread_instruction_now) {
+  perfetto::DynamicCategory category(
+      TraceLog::GetInstance()->GetCategoryGroupName(category_group_enabled));
+  auto phase = TRACE_EVENT_PHASE_END;
+  base::TimeTicks timestamp =
+      explicit_timestamps ? now : TRACE_TIME_TICKS_NOW();
+  if (thread_id && thread_id != base::PlatformThread::CurrentId()) {
+    PERFETTO_INTERNAL_LEGACY_EVENT_ON_TRACK(
+        phase, category, name, perfetto::ThreadTrack::ForThread(thread_id),
+        timestamp);
+    return;
+  }
+  PERFETTO_INTERNAL_LEGACY_EVENT_ON_TRACK(
+      phase, category, name,
+      perfetto::internal::TrackEventInternal::kDefaultTrack, timestamp);
 }
 #endif  // BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
 
@@ -632,7 +699,8 @@
   // to Perfetto yet will still be using TRACE_EVENT_API_ADD_TRACE_EVENT, so we
   // need to route these events to Perfetto using an override here. This
   // override is also used to capture internal metadata events.
-  SetAddTraceEventOverrides(&OnAddLegacyTraceEvent, nullptr, nullptr);
+  SetAddTraceEventOverrides(&OnAddLegacyTraceEvent, nullptr,
+                            &OnUpdateLegacyTraceEventDuration);
 #endif  // BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
   g_trace_log_for_testing = this;
 }
@@ -2092,6 +2160,19 @@
   process_sort_index_ = sort_index;
 }
 
+void TraceLog::set_process_name(const std::string& process_name) {
+  {
+    AutoLock lock(lock_);
+    process_name_ = process_name;
+  }
+#if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
+  auto track = perfetto::ProcessTrack::Current();
+  auto desc = track.Serialize();
+  desc.mutable_process()->set_process_name(process_name);
+  perfetto::TrackEvent::SetTrackDescriptor(track, std::move(desc));
+#endif
+}
+
 void TraceLog::UpdateProcessLabel(int label_id,
                                   const std::string& current_label) {
   if (!current_label.length())
diff --git a/base/trace_event/trace_log.h b/base/trace_event/trace_log.h
index 1f0e520..ca4a741 100644
--- a/base/trace_event/trace_log.h
+++ b/base/trace_event/trace_log.h
@@ -378,10 +378,7 @@
   void SetProcessSortIndex(int sort_index);
 
   // Sets the name of the process.
-  void set_process_name(const std::string& process_name) {
-    AutoLock lock(lock_);
-    process_name_ = process_name;
-  }
+  void set_process_name(const std::string& process_name);
 
   bool IsProcessNameEmpty() const {
     AutoLock lock(lock_);
diff --git a/base/types/id_type.h b/base/types/id_type.h
index 298dc98..e18d168 100644
--- a/base/types/id_type.h
+++ b/base/types/id_type.h
@@ -35,7 +35,8 @@
 //             a default/null value other than zero.
 //
 // IdType32<Foo> behaves just like an int32_t in the following aspects:
-// - it can be used as a key in std::map and/or std::unordered_map;
+// - it can be used as a key in std::map;
+// - it can be used as a key in std::unordered_map (see StrongAlias::Hasher);
 // - it can be used as an argument to DCHECK_EQ or streamed to LOG(ERROR);
 // - it has the same memory footprint and runtime overhead as int32_t;
 // - it can be copied by memcpy.
@@ -44,7 +45,7 @@
 // IdType32<Foo> has the following differences from a bare int32_t:
 // - it forces coercions to go through the explicit constructor and value()
 //   getter;
-// - it restricts the set of available operations (i.e. no multiplication);
+// - it restricts the set of available operations (e.g. no multiplication);
 // - it default-constructs to a null value and allows checking against the null
 //   value via is_null method.
 template <typename TypeMarker,
diff --git a/base/types/strong_alias.h b/base/types/strong_alias.h
index a80bbe4a..8c148d4 100644
--- a/base/types/strong_alias.h
+++ b/base/types/strong_alias.h
@@ -58,7 +58,7 @@
 // used) if UnderlyingType doesn't support them.
 //
 // StrongAlias only directly exposes comparison operators (for convenient use in
-// ordered containers) and a hash function (for unordered_map/set). It's
+// ordered containers) and a Hasher struct (for unordered_map/set). It's
 // impossible, without reflection, to expose all methods of the UnderlyingType
 // in StrongAlias's interface. It's also potentially unwanted (ex. you don't
 // want to be able to add two StrongAliases that represent socket handles).
@@ -117,6 +117,16 @@
   }
 
   // Hasher to use in std::unordered_map, std::unordered_set, etc.
+  //
+  // Example usage:
+  //     using MyType = base::StrongAlias<...>;
+  //     using MySet = std::unordered_set<MyType, typename MyType::Hasher>;
+  //
+  // https://google.github.io/styleguide/cppguide.html#std_hash asks to avoid
+  // defining specializations of `std::hash` - this is why the hasher needs to
+  // be explicitly specified and why the following code will *not* work:
+  //     using MyType = base::StrongAlias<...>;
+  //     using MySet = std::unordered_set<MyType>;  // This won't work.
   struct Hasher {
     using argument_type = StrongAlias;
     using result_type = std::size_t;
diff --git a/base/values.cc b/base/values.cc
index 43bc8cf..63d30f2 100644
--- a/base/values.cc
+++ b/base/values.cc
@@ -1564,10 +1564,6 @@
   list().swap(other->list());
 }
 
-ListValue* ListValue::DeepCopy() const {
-  return new ListValue(list());
-}
-
 std::unique_ptr<ListValue> ListValue::CreateDeepCopy() const {
   return std::make_unique<ListValue>(list());
 }
diff --git a/base/values.h b/base/values.h
index 57053e4..9950916 100644
--- a/base/values.h
+++ b/base/values.h
@@ -466,7 +466,7 @@
   // exist, this operation fails, leaves underlying Values untouched and returns
   // `false`. In case intermediate dictionaries become empty as a result of this
   // path removal, they will be removed as well.
-  // Note: If there is only one component in the path, use `ExtractKey()`
+  // Note: If there is only one component in the path, use `RemoveKey()`
   // instead.
   //
   // Example:
@@ -888,9 +888,6 @@
 
   // DEPRECATED, use `Value::Clone()` instead.
   // TODO(crbug.com/646113): Delete this and migrate callsites.
-  ListValue* DeepCopy() const;
-  // DEPRECATED, use `Value::Clone()` instead.
-  // TODO(crbug.com/646113): Delete this and migrate callsites.
   std::unique_ptr<ListValue> CreateDeepCopy() const;
 };
 
diff --git a/base/win/win_includes_unittest.cc b/base/win/win_includes_unittest.cc
index 31177fa..25e6f07 100644
--- a/base/win/win_includes_unittest.cc
+++ b/base/win/win_includes_unittest.cc
@@ -41,3 +41,4 @@
               "Definition mismatch.");
 static_assert(sizeof(CHROME_FORMATETC) == sizeof(FORMATETC),
               "Definition mismatch.");
+static_assert(sizeof(CHROME_LUID) == sizeof(LUID), "Definition mismatch.");
diff --git a/base/win/windows_types.h b/base/win/windows_types.h
index 7977ea7..140d579 100644
--- a/base/win/windows_types.h
+++ b/base/win/windows_types.h
@@ -151,6 +151,11 @@
   PVOID Ptr;
 };
 
+struct CHROME_LUID {
+  DWORD LowPart;
+  LONG HighPart;
+};
+
 // _WIN32_FIND_DATAW is 592 bytes and the largest built-in type in it is a
 // DWORD. The buffer declaration guarantees the correct size and alignment.
 struct CHROME_WIN32_FIND_DATA {
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 5dedf5d..b77f176 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -1217,6 +1217,7 @@
     # we build same files with same compile flag.
     # Other paths are already given in relative, no need to normalize them.
     if (is_nacl) {
+      # TODO(https://crbug.com/1231236): Use -ffile-compilation-dir= here.
       cflags += [
         "-Xclang",
         "-fdebug-compilation-dir",
@@ -1597,6 +1598,11 @@
         # TODO(thakis): https://crbug.com/617318
         # Currently goma can not handle case sensitiveness for windows well.
         cflags += [ "-Wno-nonportable-include-path" ]
+
+        if (llvm_force_head_revision) {
+          # Warns in ATL headers; see https://crbug.com/1208419.
+          cflags += [ "-Wno-null-pointer-subtraction" ]
+        }
       }
 
       if (current_toolchain == host_toolchain || !use_xcode_clang) {
@@ -2387,16 +2393,16 @@
     # gcc nacl is is_nacl && !is_clang, pnacl and nacl-clang are && is_clang.
     if (!is_nacl || is_clang) {
       cflags += [ "-g2" ]
+    }
 
+    if (!is_nacl && is_clang && !is_tsan && !is_asan) {
       # gcc generates dwarf-aranges by default on -g1 and -g2. On clang it has
       # to be manually enabled.
       #
       # It is skipped in tsan and asan because enabling it causes some
       # formatting changes in the output which would require fixing bunches
       # of expectation regexps.
-      if (is_clang && !is_tsan && !is_asan) {
-        cflags += [ "-gdwarf-aranges" ]
-      }
+      cflags += [ "-gdwarf-aranges" ]
     }
 
     if (is_apple) {
@@ -2489,12 +2495,13 @@
     # gcc nacl is is_nacl && !is_clang, pnacl and nacl-clang are && is_clang.
     if (!is_nacl || is_clang) {
       cflags += [ "-g1" ]
-
-      # See comment for -gdwarf-aranges in config("symbols").
-      if (is_clang && !is_tsan && !is_asan) {
-        cflags += [ "-gdwarf-aranges" ]
-      }
     }
+
+    if (!is_nacl && is_clang && !is_tsan && !is_asan) {
+      # See comment for -gdwarf-aranges in config("symbols").
+      cflags += [ "-gdwarf-aranges" ]
+    }
+
     ldflags = []
     if (is_android && is_clang) {
       # Android defaults to symbol_level=1 builds in production builds
diff --git a/build/config/dcheck_always_on.gni b/build/config/dcheck_always_on.gni
index e7d6a79..a63aec9 100644
--- a/build/config/dcheck_always_on.gni
+++ b/build/config/dcheck_always_on.gni
@@ -10,11 +10,25 @@
 }
 
 declare_args() {
-  # Set to true to enable dcheck in Release builds.
-  dcheck_always_on = dcheck_is_configurable
+  # Set to false to disable DCHECK in Release builds. This is enabled by default
+  # for non-official builds on the below platforms.
+  # Note: If you are here to revert because DCHECKs are failing on a specific OS
+  # please prefer excluding OSes rather than reverting . I.e. if Mac builds
+  # break badly but other platforms are reasonably stable, add "&& !is_mac"
+  # instead of reverting.
+  # TODO(crbug.com/1225701): Once this sticks, try to expand this to all
+  # platforms (incl. Fuchsia + any OS added here during rollout) and reduce it
+  # down to !is_official_builds || dcheck_is_configurable.
+  dcheck_always_on = (!is_official_build && !is_fuchsia && !is_chromeos) ||
+                     dcheck_is_configurable
 }
 
 declare_args() {
   # Set to false to disable EXPENSIVE_DCHECK()s.
+  # TODO(crbug.com/1225701): Hash out whether expensive DCHECKs need to be
+  # disabled for developers by default. There's concern that disabling these
+  # globally by default effectively reduces them to zero coverage. This is
+  # in place so that you can disable expensive DCHECKs while retaining some
+  # DCHECK coverage, which is especially important in user-facing builds.
   enable_expensive_dchecks = is_debug || dcheck_always_on
 }
diff --git a/build/config/fuchsia/test/network_capabilities.test-cmx b/build/config/fuchsia/test/network_capabilities.test-cmx
index dfeb131..1f2d121 100644
--- a/build/config/fuchsia/test/network_capabilities.test-cmx
+++ b/build/config/fuchsia/test/network_capabilities.test-cmx
@@ -4,12 +4,13 @@
       "injected-services": {
         "fuchsia.net.NameLookup": "fuchsia-pkg://fuchsia.com/dns-resolver#meta/dns-resolver.cmx",
         "fuchsia.net.interfaces.State": "fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx",
+        "fuchsia.net.name.Lookup": "fuchsia-pkg://fuchsia.com/dns-resolver#meta/dns-resolver.cmx",
         "fuchsia.posix.socket.Provider": "fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx"
       },
       "system-services": [
         "fuchsia.device.NameProvider"
-      ],
-    },
+      ]
+    }
   },
   "sandbox": {
     "features": [
@@ -17,9 +18,10 @@
     ],
     "services": [
       "fuchsia.device.NameProvider",
+      "fuchsia.net.name.Lookup",
       "fuchsia.net.NameLookup",
       "fuchsia.net.interfaces.State",
       "fuchsia.posix.socket.Provider"
     ]
   }
-}
\ No newline at end of file
+}
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 1cf70b2f..53ab7dc 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-5.20210720.1.1
+5.20210721.2.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 1cf70b2f..53ab7dc 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-5.20210720.1.1
+5.20210721.2.1
diff --git a/cc/paint/paint_canvas.h b/cc/paint/paint_canvas.h
index a97c67a2..ec98004 100644
--- a/cc/paint/paint_canvas.h
+++ b/cc/paint/paint_canvas.h
@@ -12,7 +12,8 @@
 #include "cc/paint/paint_export.h"
 #include "cc/paint/paint_image.h"
 #include "third_party/skia/include/core/SkCanvas.h"
-#include "third_party/skia/include/core/SkTextBlob.h"
+
+class SkTextBlob;
 
 namespace printing {
 class MetafileSkia;
diff --git a/cc/raster/task.cc b/cc/raster/task.cc
index 5a34ca8..9fc91ffb6 100644
--- a/cc/raster/task.cc
+++ b/cc/raster/task.cc
@@ -4,6 +4,9 @@
 
 #include "cc/raster/task.h"
 
+#include <ostream>
+#include <utility>
+
 #include "base/check.h"
 #include "base/notreached.h"
 
diff --git a/cc/tiles/picture_layer_tiling.cc b/cc/tiles/picture_layer_tiling.cc
index 0e883ad..d0f568f 100644
--- a/cc/tiles/picture_layer_tiling.cc
+++ b/cc/tiles/picture_layer_tiling.cc
@@ -18,6 +18,7 @@
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/traced_value.h"
 #include "cc/base/math_util.h"
+#include "cc/layers/picture_layer_impl.h"
 #include "cc/raster/raster_source.h"
 #include "cc/tiles/prioritized_tile.h"
 #include "cc/tiles/tile.h"
@@ -931,9 +932,16 @@
   DCHECK_EQ(ComputePriorityRectTypeForTile(tile), priority_rect_type);
   DCHECK_EQ(TileAt(tile->tiling_i_index(), tile->tiling_j_index()), tile);
 
-  TilePriority::PriorityBin priority_bin = client_->HasValidTilePriorities()
-                                               ? TilePriority::NOW
-                                               : TilePriority::EVENTUALLY;
+  TilePriority::PriorityBin priority_bin;
+  if (client_->HasValidTilePriorities()) {
+    // Occluded tiles are given a lower PriorityBin to ensure they are evicted
+    // before non-occluded tiles.
+    priority_bin =
+        IsTileOccluded(tile) ? TilePriority::SOON : TilePriority::NOW;
+  } else {
+    priority_bin = TilePriority::EVENTUALLY;
+  }
+
   switch (priority_rect_type) {
     case VISIBLE_RECT:
     case PENDING_VISIBLE_RECT:
diff --git a/cc/tiles/tile_manager_unittest.cc b/cc/tiles/tile_manager_unittest.cc
index b4aa819a..08f4657 100644
--- a/cc/tiles/tile_manager_unittest.cc
+++ b/cc/tiles/tile_manager_unittest.cc
@@ -1770,6 +1770,106 @@
   }
 }
 
+class TileManagerOcclusionTest : public TileManagerTest {
+ public:
+  LayerTreeSettings CreateSettings() override {
+    auto settings = TestLayerTreeHostBase::CreateSettings();
+    settings.create_low_res_tiling = true;
+    settings.use_occlusion_for_tile_prioritization = true;
+    return settings;
+  }
+
+  void PrepareTilesAndWaitUntilDone(
+      const GlobalStateThatImpactsTilePriority& state) {
+    base::RunLoop run_loop;
+    EXPECT_CALL(MockHostImpl(), NotifyAllTileTasksCompleted())
+        .WillOnce(testing::Invoke([&run_loop]() { run_loop.Quit(); }));
+    tile_manager()->PrepareTiles(state);
+    run_loop.Run();
+    tile_manager()->CheckForCompletedTasks();
+  }
+
+  TileManager* tile_manager() { return host_impl()->tile_manager(); }
+};
+
+TEST_F(TileManagerOcclusionTest, OccludedTileEvictedForVisibleTile) {
+  gfx::Size layer_bounds(256, 256);
+  host_impl()->active_tree()->SetDeviceViewportRect(gfx::Rect(layer_bounds));
+
+  SetupPendingTree(FakeRasterSource::CreateFilledWithText(layer_bounds));
+  const int initial_picture_id = pending_layer()->id();
+  ActivateTree();
+
+  GlobalStateThatImpactsTilePriority global_state =
+      host_impl()->global_tile_state();
+  const LayerTreeSettings settings = CreateSettings();
+  global_state.hard_memory_limit_in_bytes =
+      global_state.soft_memory_limit_in_bytes =
+          settings.default_tile_size.GetArea() * 4 + 1;
+
+  // Call PrepareTiles and wait for it to complete. It's necessary to wait for
+  // completion so that the resource is pushed to the tile, which is used
+  // in calculating eviction.
+  PrepareTilesAndWaitUntilDone(global_state);
+
+  // At this point the initial PictureLayerImpl should have a Tile with a
+  // resource.
+  {
+    PictureLayerImpl* initial_layer = static_cast<PictureLayerImpl*>(
+        host_impl()->active_tree()->LayerById(initial_picture_id));
+    ASSERT_NE(initial_layer, nullptr);
+    ASSERT_EQ(1u, initial_layer->picture_layer_tiling_set()->num_tilings());
+    std::vector<Tile*> initial_layer_tiles =
+        initial_layer->picture_layer_tiling_set()
+            ->tiling_at(0)
+            ->AllTilesForTesting();
+    ASSERT_EQ(1u, initial_layer_tiles.size());
+    EXPECT_TRUE(initial_layer_tiles[0]->draw_info().has_resource());
+  }
+
+  // Add another layer on top. As there is only enough memory for one tile,
+  // the top most tile should get a raster task and the bottom layer should
+  // not.
+  SetupPendingTree(FakeRasterSource::CreateFilledWithText(layer_bounds));
+
+  // Advance the frame to ensure PictureLayerTilingSet applies the occlusion to
+  // the PictureLayerTiling. The amount of time advanced doesn't matter.
+  host_impl()->AdvanceToNextFrame(base::TimeDelta::FromSeconds(2));
+
+  auto* picture_layer = AddLayer<FakePictureLayerImpl>(
+      host_impl()->pending_tree(),
+      FakeRasterSource::CreateFilledWithText(layer_bounds));
+  const int top_most_layer_id = picture_layer->id();
+  picture_layer->SetDrawsContent(true);
+  picture_layer->SetContentsOpaque(true);
+  CopyProperties(pending_layer(), picture_layer);
+  ActivateTree();
+  PrepareTilesAndWaitUntilDone(global_state);
+  {
+    PictureLayerImpl* initial_layer = static_cast<PictureLayerImpl*>(
+        host_impl()->active_tree()->LayerById(initial_picture_id));
+    ASSERT_NE(initial_layer, nullptr);
+    ASSERT_EQ(1u, initial_layer->picture_layer_tiling_set()->num_tilings());
+    std::vector<Tile*> initial_layer_tiles =
+        initial_layer->picture_layer_tiling_set()
+            ->tiling_at(0)
+            ->AllTilesForTesting();
+    ASSERT_EQ(1u, initial_layer_tiles.size());
+    EXPECT_FALSE(initial_layer_tiles[0]->draw_info().has_resource());
+
+    PictureLayerImpl* top_most_layer = static_cast<PictureLayerImpl*>(
+        host_impl()->active_tree()->LayerById(top_most_layer_id));
+    ASSERT_NE(top_most_layer, nullptr);
+    ASSERT_EQ(1u, top_most_layer->picture_layer_tiling_set()->num_tilings());
+    std::vector<Tile*> top_most_layer_tiles =
+        top_most_layer->picture_layer_tiling_set()
+            ->tiling_at(0)
+            ->AllTilesForTesting();
+    ASSERT_EQ(1u, top_most_layer_tiles.size());
+    EXPECT_TRUE(top_most_layer_tiles[0]->draw_info().has_resource());
+  }
+}
+
 class PixelInspectTileManagerTest : public TileManagerTest {
  public:
   ~PixelInspectTileManagerTest() override {
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index 379c394..d5c1afa 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -1060,6 +1060,7 @@
       "//base/allocator:buildflags",
       "//build:chromeos_buildflags",
       "//chrome/app:command_ids",
+      "//chrome/app:notification_metrics",
       "//chrome/common:buildflags",
       "//chrome/common/profiler",
       "//components/crash/core/app",
diff --git a/chrome/VERSION b/chrome/VERSION
index e99441e..3cd34dd 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=94
 MINOR=0
-BUILD=4582
+BUILD=4583
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index dee00d0..762b577 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -3095,6 +3095,21 @@
       _bundle_smoke_test_extra_args + _bundle_fake_modules_smoke_test_extra_args
 }
 
+group("chrome_nocompile_tests") {
+  # Tests which check that build errors are thrown when expected and that build
+  # validation tools (ex: lint) do not get silently disabled.
+  testonly = true
+
+  # No-compile tests use an output directory dedicated to no-compile tests.
+  # Put new test suites in //build/android/test/nocompile_gn if possible in
+  # order to share the target output directory and avoid running 'gn gen'
+  # for each android_nocompile_test_suite().
+  deps = [
+    "//build/android/test:android_nocompile_tests",
+    "//tools/android/errorprone_plugin/test:errorprone_plugin_tests",
+  ]
+}
+
 script_test("chrome_public_wpt") {
   script = "//testing/scripts/run_android_wpt.py"
   args = [
@@ -3465,6 +3480,7 @@
     "java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialsServiceUtils.java",
     "java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowBridge.java",
     "java/src/org/chromium/chrome/browser/autofill/AutofillLogger.java",
+    "java/src/org/chromium/chrome/browser/autofill/AutofillMessageConfirmFlowBridge.java",
     "java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowBridge.java",
     "java/src/org/chromium/chrome/browser/autofill/AutofillPopupBridge.java",
     "java/src/org/chromium/chrome/browser/autofill/AutofillSnackbarController.java",
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index 3f07afc..0f080a2 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -568,6 +568,7 @@
   "java/res/layout/auto_sign_in_first_run_dialog.xml",
   "java/res/layout/autofill_billing_address_dropdown.xml",
   "java/res/layout/autofill_card_unmask_prompt.xml",
+  "java/res/layout/autofill_cc_details.xml",
   "java/res/layout/autofill_editor_base.xml",
   "java/res/layout/autofill_editor_base_buttons.xml",
   "java/res/layout/autofill_expiration_date_fix_flow.xml",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index dec95ae..47fdb19 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -100,6 +100,7 @@
   "java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowBridge.java",
   "java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowPrompt.java",
   "java/src/org/chromium/chrome/browser/autofill/AutofillLogger.java",
+  "java/src/org/chromium/chrome/browser/autofill/AutofillMessageConfirmFlowBridge.java",
   "java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowBridge.java",
   "java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowPrompt.java",
   "java/src/org/chromium/chrome/browser/autofill/AutofillPopupBridge.java",
@@ -109,6 +110,7 @@
   "java/src/org/chromium/chrome/browser/autofill/CardUnmaskPrompt.java",
   "java/src/org/chromium/chrome/browser/autofill/CreditCardScanner.java",
   "java/src/org/chromium/chrome/browser/autofill/CreditCardScannerBridge.java",
+  "java/src/org/chromium/chrome/browser/autofill/LegalMessageLine.java",
   "java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java",
   "java/src/org/chromium/chrome/browser/autofill/PhoneNumberUtil.java",
   "java/src/org/chromium/chrome/browser/autofill/SaveUpdateAddressProfilePrompt.java",
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantActionsCarouselUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantActionsCarouselUiTest.java
index 5050d84..e58e4da 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantActionsCarouselUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantActionsCarouselUiTest.java
@@ -80,7 +80,8 @@
     @Test
     @MediumTest
     public void testInitialState() throws Exception {
-        AssistantCarouselModel model = new AssistantCarouselModel();
+        AssistantCarouselModel model =
+                TestThreadUtils.runOnUiThreadBlocking(AssistantCarouselModel::new);
         AssistantActionsCarouselCoordinator coordinator = createCoordinator(model);
 
         assertThat(model.get(AssistantCarouselModel.CHIPS).size(), is(0));
@@ -91,7 +92,8 @@
     @Test
     @MediumTest
     public void testAddSingleChip() throws Exception {
-        AssistantCarouselModel model = new AssistantCarouselModel();
+        AssistantCarouselModel model =
+                TestThreadUtils.runOnUiThreadBlocking(AssistantCarouselModel::new);
         AssistantActionsCarouselCoordinator coordinator = createCoordinator(model);
 
         TestThreadUtils.runOnUiThreadBlocking(
@@ -112,7 +114,8 @@
     @Test
     @MediumTest
     public void testAddMultipleChips() throws Exception {
-        AssistantCarouselModel model = new AssistantCarouselModel();
+        AssistantCarouselModel model =
+                TestThreadUtils.runOnUiThreadBlocking(AssistantCarouselModel::new);
         AssistantActionsCarouselCoordinator coordinator = createCoordinator(model);
 
         // Note: this should be a small number that fits on screen without scrolling.
@@ -140,7 +143,8 @@
     @Test
     @MediumTest
     public void testCancelChipAlwaysVisible() throws Exception {
-        AssistantCarouselModel model = new AssistantCarouselModel();
+        AssistantCarouselModel model =
+                TestThreadUtils.runOnUiThreadBlocking(AssistantCarouselModel::new);
         AssistantActionsCarouselCoordinator coordinator = createCoordinator(model);
 
         // Note: this should be a large number that does not fit on screen without scrolling.
@@ -172,7 +176,8 @@
     @Test
     @MediumTest
     public void testMoveChip() throws Exception {
-        AssistantCarouselModel model = new AssistantCarouselModel();
+        AssistantCarouselModel model =
+                TestThreadUtils.runOnUiThreadBlocking(AssistantCarouselModel::new);
         AssistantActionsCarouselCoordinator coordinator = createCoordinator(model);
 
         List<AssistantChip> chips = new ArrayList<>();
@@ -207,7 +212,8 @@
     @MediumTest
     public void testSuppliedNonEmptyContentDescriptionIsUsed() throws Exception {
         String contentDescription = "Test content description";
-        AssistantCarouselModel model = new AssistantCarouselModel();
+        AssistantCarouselModel model =
+                TestThreadUtils.runOnUiThreadBlocking(AssistantCarouselModel::new);
         AssistantActionsCarouselCoordinator coordinator = createCoordinator(model);
 
         TestThreadUtils.runOnUiThreadBlocking(
@@ -230,7 +236,8 @@
     @MediumTest
     public void testSuppliedEmptyContentDescriptionIsUsed() throws Exception {
         String contentDescription = "";
-        AssistantCarouselModel model = new AssistantCarouselModel();
+        AssistantCarouselModel model =
+                TestThreadUtils.runOnUiThreadBlocking(AssistantCarouselModel::new);
         AssistantActionsCarouselCoordinator coordinator = createCoordinator(model);
 
         TestThreadUtils.runOnUiThreadBlocking(
@@ -253,7 +260,8 @@
     @MediumTest
     public void testWhenNullContentDescriptionIsSuppliedChipTextIsUsed() throws Exception {
         String chipText = "Chip Text";
-        AssistantCarouselModel model = new AssistantCarouselModel();
+        AssistantCarouselModel model =
+                TestThreadUtils.runOnUiThreadBlocking(AssistantCarouselModel::new);
         AssistantActionsCarouselCoordinator coordinatorNonEmptyChipText = createCoordinator(model);
 
         TestThreadUtils.runOnUiThreadBlocking(
@@ -277,7 +285,8 @@
     @MediumTest
     public void testWhenNullContentDescriptionIsSuppliedChipTextOrIconDescriptionIsUsed()
             throws Exception {
-        AssistantCarouselModel model = new AssistantCarouselModel();
+        AssistantCarouselModel model =
+                TestThreadUtils.runOnUiThreadBlocking(AssistantCarouselModel::new);
         AssistantActionsCarouselCoordinator coordinatorEmptyChipText = createCoordinator(model);
 
         TestThreadUtils.runOnUiThreadBlocking(
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataUiTest.java
index 97367dbf5a..bffab00 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataUiTest.java
@@ -119,6 +119,10 @@
         mDefaultContactFullOptions.mMaxNumberLines = 2;
     }
 
+    private AssistantCollectUserDataModel createCollectUserDataModel() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(AssistantCollectUserDataModel::new);
+    }
+
     /** Creates a coordinator for use in UI tests, and adds it to the global view hierarchy. */
     private AssistantCollectUserDataCoordinator createCollectUserDataCoordinator(
             AssistantCollectUserDataModel model) throws Exception {
@@ -154,7 +158,7 @@
     @Test
     @MediumTest
     public void testInitialState() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
 
         /* Test initial model state. */
@@ -208,7 +212,7 @@
     @Test
     @MediumTest
     public void testSectionVisibility() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper
                 .ViewHolder viewHolder = TestThreadUtils.runOnUiThreadBlocking(
@@ -302,7 +306,7 @@
     @Test
     @MediumTest
     public void testEmptyPaymentRequest() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
                 new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
@@ -418,7 +422,7 @@
     @Test
     @MediumTest
     public void testContactDetailsUpdates() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper
                 .ViewHolder viewHolder = TestThreadUtils.runOnUiThreadBlocking(
@@ -490,7 +494,7 @@
     @Test
     @MediumTest
     public void testPaymentMethodsUpdates() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper
                 .ViewHolder viewHolder = TestThreadUtils.runOnUiThreadBlocking(
@@ -558,7 +562,7 @@
     @Test
     @MediumTest
     public void testPaymentMethodsUpdatesFromWebContents() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper
                 .ViewHolder viewHolder = TestThreadUtils.runOnUiThreadBlocking(
@@ -632,7 +636,7 @@
                         "4111111111111111", "1111", "12", "2050", "visa", R.drawable.visa_card,
                         /* billingAddressId= */ "GUID", /* serverId= */ "");
 
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
                 new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
@@ -797,7 +801,7 @@
                 /* requestName= */ true,
                 /* requestPhone= */ true, /* requestEmail= */ true);
 
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
                 new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
@@ -854,7 +858,7 @@
     @Test
     @MediumTest
     public void testTermsAndConditions() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
                 new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
@@ -919,7 +923,7 @@
     @Test
     @MediumTest
     public void testTermsRequireReview() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         createCollectUserDataCoordinator(model);
 
         // Setting a text from "backend".
@@ -936,7 +940,7 @@
     @Test
     @MediumTest
     public void testInfoSectionText() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper
                 .ViewHolder viewHolder = TestThreadUtils.runOnUiThreadBlocking(
@@ -959,7 +963,7 @@
     @Test
     @MediumTest
     public void testPrivacyNotice() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper
                 .ViewHolder viewHolder = TestThreadUtils.runOnUiThreadBlocking(
@@ -982,7 +986,7 @@
     @Test
     @MediumTest
     public void testDateRangeLocaleUS() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         Locale locale = LocaleUtils.forLanguageTag("en-US");
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(
                 model, locale, new SimpleDateFormat("MMM d, yyyy", locale));
@@ -1049,7 +1053,7 @@
     @Test
     @MediumTest
     public void testDateRangeLocaleDE() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         Locale locale = LocaleUtils.forLanguageTag("de-DE");
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(
                 model, locale, new SimpleDateFormat("dd.MM.yyyy", locale));
@@ -1116,7 +1120,7 @@
     @Test
     @MediumTest
     public void testDateOrTimeNotSet() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         Locale locale = LocaleUtils.forLanguageTag("en-US");
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(
                 model, locale, new SimpleDateFormat("MMM d, yyyy", locale));
@@ -1198,7 +1202,7 @@
     @Test
     @MediumTest
     public void testDateRangePopups() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         Locale locale = LocaleUtils.forLanguageTag("en-US");
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(
                 model, locale, new SimpleDateFormat("MMM d, yyyy", locale));
@@ -1281,7 +1285,7 @@
     @Test
     @MediumTest
     public void testAdditionalStaticSections() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
 
         List<AssistantAdditionalSectionFactory> prependedSections = new ArrayList<>();
@@ -1331,7 +1335,7 @@
     @Test
     @MediumTest
     public void testAdditionalTextInputSections() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
                 new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
@@ -1374,7 +1378,7 @@
     @Test
     @MediumTest
     public void testLoginSectionInfoPopup() throws Exception {
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
                 new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
@@ -1403,7 +1407,7 @@
     @MediumTest
     public void testSuppliedNonEmptyEditContentDescriptionIsUsed() throws Exception {
         String contentDescription = "Description of edit button";
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
                 new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
@@ -1427,7 +1431,7 @@
     @MediumTest
     public void testSuppliedEmptyEditContentDescriptionIsUsed() throws Exception {
         String contentDescription = "";
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
                 new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
@@ -1452,7 +1456,7 @@
     public void testWhenNullEditContentDescriptionIsSuppliedIconDescriptionIsUsed()
             throws Exception {
         String contentDescription = null;
-        AssistantCollectUserDataModel model = new AssistantCollectUserDataModel();
+        AssistantCollectUserDataModel model = createCollectUserDataModel();
         AssistantCollectUserDataCoordinator coordinator = createCollectUserDataCoordinator(model);
         AutofillAssistantCollectUserDataTestHelper.MockDelegate delegate =
                 new AutofillAssistantCollectUserDataTestHelper.MockDelegate();
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantDetailsUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantDetailsUiTest.java
index 79f7d49..4431bcc 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantDetailsUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantDetailsUiTest.java
@@ -50,6 +50,7 @@
 import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.Arrays;
 
@@ -104,6 +105,10 @@
         }
     }
 
+    private AssistantDetailsModel createModel() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(AssistantDetailsModel::new);
+    }
+
     /** Creates a coordinator for use in UI tests, and adds it to the global view hierarchy. */
     private AssistantDetailsCoordinator createCoordinator(AssistantDetailsModel model)
             throws Exception {
@@ -138,7 +143,7 @@
     @Test
     @MediumTest
     public void testInitialState() throws Exception {
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
 
         assertThat(model.get(AssistantDetailsModel.DETAILS).size(), is(0));
@@ -149,7 +154,7 @@
     @Test
     @MediumTest
     public void testVisibility() throws Exception {
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
         ViewMatchers viewMatchers = new ViewMatchers(coordinator.getView());
 
@@ -172,7 +177,7 @@
     @Test
     @MediumTest
     public void testAccessibility() throws Exception {
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
         ViewMatchers viewMatchers = new ViewMatchers(coordinator.getView());
 
@@ -189,7 +194,7 @@
     @Test
     @MediumTest
     public void testAccessibilityEmpty() throws Exception {
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
         ViewMatchers viewMatchers = new ViewMatchers(coordinator.getView());
 
@@ -205,7 +210,7 @@
     @Test
     @MediumTest
     public void testTitle() throws Exception {
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
         ViewMatchers viewMatchers = new ViewMatchers(coordinator.getView());
 
@@ -265,7 +270,7 @@
     @Test
     @MediumTest
     public void testDescriptionLine1() throws Exception {
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
         ViewMatchers viewMatchers = new ViewMatchers(coordinator.getView());
 
@@ -286,7 +291,7 @@
     @Test
     @MediumTest
     public void testDescriptionLine2() throws Exception {
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
         ViewMatchers viewMatchers = new ViewMatchers(coordinator.getView());
 
@@ -307,7 +312,7 @@
     @Test
     @MediumTest
     public void testDescriptionLine3() throws Exception {
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
         ViewMatchers viewMatchers = new ViewMatchers(coordinator.getView());
 
@@ -328,7 +333,7 @@
     @Test
     @MediumTest
     public void testPriceAttribution() throws Exception {
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
         ViewMatchers viewMatchers = new ViewMatchers(coordinator.getView());
 
@@ -349,7 +354,7 @@
     @Test
     @MediumTest
     public void testHighlighting() throws Exception {
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
         ViewMatchers viewMatchers = new ViewMatchers(coordinator.getView());
 
@@ -417,7 +422,7 @@
     @Test
     @MediumTest
     public void testStyleSpans() throws Exception {
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         createCoordinator(model);
 
         setDetails(model,
@@ -442,7 +447,7 @@
     @Test
     @MediumTest
     public void testPlaceholders() throws Exception {
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
         ViewMatchers viewMatchers = new ViewMatchers(coordinator.getView());
 
@@ -477,7 +482,7 @@
     @Test
     @MediumTest
     public void testMultipleDetails() throws Exception {
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
         ViewMatchers viewMatchers = new ViewMatchers(coordinator.getView());
 
@@ -498,7 +503,7 @@
     @MediumTest
     public void testPlaceholdersAnimation() throws Exception {
         // Test that the placeholders animation is running only when details have placeholders.
-        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsModel model = createModel();
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
 
         assertThat(coordinator.isRunningPlaceholdersAnimationForTesting(), is(false));
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantHeaderUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantHeaderUiTest.java
index 164bd89..e2a9a208 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantHeaderUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantHeaderUiTest.java
@@ -97,6 +97,10 @@
         return mCustomTabActivityTestRule.getActivity();
     }
 
+    private AssistantHeaderModel createModel() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(AssistantHeaderModel::new);
+    }
+
     /** Creates a coordinator for use in UI tests, and adds it to the global view hierarchy. */
     private AssistantHeaderCoordinator createCoordinator(AssistantHeaderModel model) {
         return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
@@ -117,7 +121,7 @@
     @Test
     @MediumTest
     public void testInitialState() {
-        AssistantHeaderModel model = new AssistantHeaderModel();
+        AssistantHeaderModel model = createModel();
         AssistantHeaderCoordinator coordinator = createCoordinator(model);
         ViewHolder viewHolder = new ViewHolder(coordinator.getView());
 
@@ -137,7 +141,7 @@
     @Test
     @MediumTest
     public void testSimpleModelChanges() {
-        AssistantHeaderModel model = new AssistantHeaderModel();
+        AssistantHeaderModel model = createModel();
         AssistantHeaderCoordinator coordinator = createCoordinator(model);
         ViewHolder viewHolder = new ViewHolder(coordinator.getView());
 
@@ -162,7 +166,7 @@
     @Test
     @MediumTest
     public void testProgressBarVisibility() {
-        AssistantHeaderModel model = new AssistantHeaderModel();
+        AssistantHeaderModel model = createModel();
         AssistantHeaderCoordinator coordinator = createCoordinator(model);
         ViewHolder viewHolder = new ViewHolder(coordinator.getView());
 
@@ -203,7 +207,7 @@
     @Test
     @MediumTest
     public void testChip() {
-        AssistantHeaderModel model = new AssistantHeaderModel();
+        AssistantHeaderModel model = createModel();
         AssistantHeaderCoordinator coordinator = createCoordinator(model);
 
         String chipText = "Hello World";
@@ -235,7 +239,7 @@
     @Test
     @MediumTest
     public void testProfileImageMenu() {
-        AssistantHeaderModel model = new AssistantHeaderModel();
+        AssistantHeaderModel model = createModel();
         AssistantHeaderCoordinator coordinator = createCoordinator(model);
         ViewHolder viewHolder = new ViewHolder(coordinator.getView());
 
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInfoBoxUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInfoBoxUiTest.java
index 30f9c6c1..9a4c26d 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInfoBoxUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInfoBoxUiTest.java
@@ -53,6 +53,10 @@
         return coordinator.getView().findViewById(R.id.info_box_explanation);
     }
 
+    private AssistantInfoBoxModel createModel() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(AssistantInfoBoxModel::new);
+    }
+
     /** Creates a coordinator for use in UI tests, and adds it to the global view hierarchy. */
     private AssistantInfoBoxCoordinator createCoordinator(AssistantInfoBoxModel model)
             throws Exception {
@@ -81,7 +85,7 @@
     @Test
     @MediumTest
     public void testInitialState() throws Exception {
-        AssistantInfoBoxModel model = new AssistantInfoBoxModel();
+        AssistantInfoBoxModel model = createModel();
         AssistantInfoBoxCoordinator coordinator = createCoordinator(model);
 
         assertThat(model.get(AssistantInfoBoxModel.INFO_BOX), nullValue());
@@ -92,7 +96,7 @@
     @Test
     @MediumTest
     public void testMessageNoImage() throws Exception {
-        AssistantInfoBoxModel model = new AssistantInfoBoxModel();
+        AssistantInfoBoxModel model = createModel();
         AssistantInfoBoxCoordinator coordinator = createCoordinator(model);
         AssistantInfoBox infoBox = new AssistantInfoBox("", "Message");
 
@@ -116,7 +120,7 @@
     @Test
     @MediumTest
     public void testImage() throws Exception {
-        AssistantInfoBoxModel model = new AssistantInfoBoxModel();
+        AssistantInfoBoxModel model = createModel();
         AssistantInfoBoxCoordinator coordinator = createCoordinator(model);
         AssistantInfoBox infoBox = new AssistantInfoBox("x", "Message");
 
@@ -131,7 +135,7 @@
     @Test
     @MediumTest
     public void testVisibility() throws Exception {
-        AssistantInfoBoxModel model = new AssistantInfoBoxModel();
+        AssistantInfoBoxModel model = createModel();
         AssistantInfoBoxCoordinator coordinator = createCoordinator(model);
         AssistantInfoBox infoBox = new AssistantInfoBox("", "");
 
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInterruptIntegrationTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInterruptIntegrationTest.java
index eb672c1..e4b4fc5 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInterruptIntegrationTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantInterruptIntegrationTest.java
@@ -35,7 +35,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantTestService.ScriptsReturnMode;
 import org.chromium.chrome.browser.autofill_assistant.proto.ActionProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.AutofillFormatProto;
@@ -119,7 +118,6 @@
 
     @Test
     @MediumTest
-    @DisabledTest(message = "https://crbug.com/1219553")
     public void testInterruptClicksElementDuringPrompt() throws Exception {
         ArrayList<AutofillAssistantTestScript> scripts = new ArrayList<>();
         SelectorProto touch_area_one = toCssSelector("#touch_area_one");
@@ -222,7 +220,6 @@
 
     @Test
     @MediumTest
-    @DisabledTest(message = "https://crbug.com/1222969")
     public void testInterruptCicksElementDuringShowGenericUi() throws Exception {
         ArrayList<AutofillAssistantTestScript> scripts = new ArrayList<>();
         SelectorProto touch_area_one = toCssSelector("#touch_area_one");
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantOverlayUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantOverlayUiTest.java
index 3636711..ffd8869 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantOverlayUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantOverlayUiTest.java
@@ -51,6 +51,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.Collections;
 import java.util.concurrent.ExecutionException;
@@ -83,6 +84,10 @@
         return mTestRule.getWebContents();
     }
 
+    private AssistantOverlayModel createModel() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(AssistantOverlayModel::new);
+    }
+
     /** Creates a coordinator for use in UI tests with a default, non-null overlay image. */
     private AssistantOverlayCoordinator createCoordinator(AssistantOverlayModel model)
             throws ExecutionException {
@@ -109,7 +114,7 @@
     @Test
     @MediumTest
     public void testInitialState() throws Exception {
-        AssistantOverlayModel model = new AssistantOverlayModel();
+        AssistantOverlayModel model = createModel();
         AssistantOverlayCoordinator coordinator = createCoordinator(model);
 
         assertScrimDisplayed(false);
@@ -121,7 +126,7 @@
     @Test
     @MediumTest
     public void testFullOverlay() throws Exception {
-        AssistantOverlayModel model = new AssistantOverlayModel();
+        AssistantOverlayModel model = createModel();
         AssistantOverlayCoordinator coordinator = createCoordinator(model);
 
         runOnUiThreadBlocking(
@@ -141,7 +146,7 @@
     @Test
     @MediumTest
     public void testFullOverlayWithImage() throws Exception {
-        AssistantOverlayModel model = new AssistantOverlayModel();
+        AssistantOverlayModel model = createModel();
         AssistantOverlayCoordinator coordinator = createCoordinator(model);
 
         AssistantOverlayImage image = new AssistantOverlayImage(
@@ -158,7 +163,7 @@
     @Test
     @MediumTest
     public void testPartialOverlay() throws Exception {
-        AssistantOverlayModel model = new AssistantOverlayModel();
+        AssistantOverlayModel model = createModel();
         AssistantOverlayCoordinator coordinator = createCoordinator(model);
 
         // Partial overlay, no touchable areas: equivalent to full overlay.
@@ -196,7 +201,7 @@
     @Test
     @MediumTest
     public void testSimpleScrollPartialOverlay() throws Exception {
-        AssistantOverlayModel model = new AssistantOverlayModel();
+        AssistantOverlayModel model = createModel();
         createCoordinator(model);
 
         ChromeTabUtils.waitForInteractable(mTestRule.getActivity().getActivityTab());
@@ -221,7 +226,7 @@
     @Test
     @MediumTest
     public void testOverlayImageDoesNotCrashIfValid() throws Exception {
-        AssistantOverlayModel model = new AssistantOverlayModel();
+        AssistantOverlayModel model = createModel();
         Bitmap bitmap = BitmapFactory.decodeResource(mTestRule.getActivity().getResources(),
                 org.chromium.chrome.autofill_assistant.R.drawable.btn_close);
         assertThat(bitmap, notNullValue());
@@ -241,7 +246,7 @@
     @Test
     @MediumTest
     public void testOverlayDoesNotCrashIfImageFailsToLoad() throws Exception {
-        AssistantOverlayModel model = new AssistantOverlayModel();
+        AssistantOverlayModel model = createModel();
         AssistantOverlayCoordinator coordinator =
                 createCoordinator(model, /* overlayImage = */ null);
 
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java
index e36ec970..f88c0cb 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPersonalDataManagerTest.java
@@ -890,7 +890,6 @@
      */
     @Test
     @MediumTest
-    @FlakyTest(message = "https://crbug.com/1219046")
     public void testCreateShippingAddressAndCreditCard() {
         ArrayList<ActionProto> list = new ArrayList<>();
         list.add(ActionProto.newBuilder()
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
index c3821d4..a72201b 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
@@ -779,11 +779,11 @@
                 mSecondaryTasksSurfacePropertyModel.set(IS_INCOGNITO, mIsIncognito);
             }
             if (mSecondaryTasksSurfaceController != null) {
-                mSecondaryTasksSurfaceController.showOverview(false);
+                mSecondaryTasksSurfaceController.showOverview(/* animate = */ true);
             }
         } else {
             if (mSecondaryTasksSurfaceController != null) {
-                mSecondaryTasksSurfaceController.hideOverview(false);
+                mSecondaryTasksSurfaceController.hideOverview(/* animate = */ false);
             }
         }
         mPropertyModel.set(IS_SECONDARY_SURFACE_VISIBLE, isVisible);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java
index 37ded69..3f43f5a 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java
@@ -52,6 +52,7 @@
  */
 public class TabGridDialogView extends FrameLayout {
     private static final int DIALOG_ANIMATION_DURATION = 300;
+    private static final int DIALOG_UNGROUP_ALPHA_ANIMATION_DURATION = 200;
     private static final int DIALOG_ALPHA_ANIMATION_DURATION = 150;
     private static final int CARD_FADE_ANIMATION_DURATION = 50;
     private static Callback<RectF> sSourceRectCallbackForTesting;
@@ -221,9 +222,8 @@
             }
         };
 
-        mUngroupBarShow =
-                ObjectAnimator.ofFloat(mUngroupBar, View.TRANSLATION_Y, mUngroupBarHeight, 0);
-        mUngroupBarShow.setDuration(DIALOG_ANIMATION_DURATION);
+        mUngroupBarShow = ObjectAnimator.ofFloat(mUngroupBar, View.ALPHA, 0f, 1f);
+        mUngroupBarShow.setDuration(DIALOG_UNGROUP_ALPHA_ANIMATION_DURATION);
         mUngroupBarShow.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
         mUngroupBarShow.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -233,6 +233,7 @@
                 }
                 mCurrentUngroupBarAnimator = mUngroupBarShow;
                 mUngroupBar.setVisibility(View.VISIBLE);
+                mUngroupBar.setAlpha(0f);
             }
 
             @Override
@@ -241,9 +242,8 @@
             }
         });
 
-        mUngroupBarHide =
-                ObjectAnimator.ofFloat(mUngroupBar, View.TRANSLATION_Y, 0, mUngroupBarHeight);
-        mUngroupBarHide.setDuration(DIALOG_ANIMATION_DURATION);
+        mUngroupBarHide = ObjectAnimator.ofFloat(mUngroupBar, View.ALPHA, 1f, 0f);
+        mUngroupBarHide.setDuration(DIALOG_UNGROUP_ALPHA_ANIMATION_DURATION);
         mUngroupBarHide.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN_INTERPOLATOR);
         mUngroupBarHide.addListener(new AnimatorListenerAdapter() {
             @Override
diff --git a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java
index 11e8a052..488ca37 100644
--- a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java
+++ b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java
@@ -131,7 +131,7 @@
             new ChromeTabbedActivityTestRule();
 
     private final FakeAccountManagerFacade mFakeAccountManagerFacade =
-            new FakeAccountManagerFacade(null) {
+            new FakeAccountManagerFacade() {
                 @Override
                 public Promise<List<Account>> getAccounts() {
                     // Attention. When cache is not populated, the Promise shouldn't be fulfilled.
diff --git a/chrome/android/java/res/layout/autofill_cc_details.xml b/chrome/android/java/res/layout/autofill_cc_details.xml
new file mode 100644
index 0000000..b58b9e3
--- /dev/null
+++ b/chrome/android/java/res/layout/autofill_cc_details.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/cc_details"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:gravity="center_vertical" >
+
+    <ImageView
+        android:id="@+id/google_pay_logo"
+        app:srcCompat="@drawable/google_pay"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no" />
+
+    <ImageView
+        android:id="@+id/message_divider"
+        android:background="@color/divider_line_bg_color"
+        android:importantForAccessibility="no"
+        android:layout_height="match_parent"
+        android:layout_width="1dp"
+        android:layout_marginStart="15dp"
+        android:layout_marginEnd="15dp" />
+
+    <TextView
+        android:id="@+id/cc_details_masked"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.TextMedium.Primary" />
+</LinearLayout>
diff --git a/chrome/android/java/res/layout/autofill_expiration_date_fix_flow.xml b/chrome/android/java/res/layout/autofill_expiration_date_fix_flow.xml
index 2e834dd..26e9121 100644
--- a/chrome/android/java/res/layout/autofill_expiration_date_fix_flow.xml
+++ b/chrome/android/java/res/layout/autofill_expiration_date_fix_flow.xml
@@ -16,12 +16,12 @@
     android:layout_marginTop="6dp"
     android:orientation="vertical">
 
-    <TextView
-        android:id="@+id/cc_details_masked"
+    <include
+        layout="@layout/autofill_cc_details"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:textAppearance="@style/TextAppearance.TextMedium.Primary"
-        android:layout_marginBottom="10dp"/>
+        android:layout_marginBottom="10dp"
+    />
 
     <LinearLayout
         android:layout_width="match_parent"
@@ -29,7 +29,7 @@
         android:focusable="true"
         android:gravity="center_vertical"
         android:orientation="horizontal">
-        
+
         <!-- TODO(crbug.com/900912): Fix and remove lint ignore -->
         <EditText
             tools:ignore="Autofill,LabelFor"
@@ -69,4 +69,12 @@
         android:gravity="start"
         android:textAppearance="@style/TextAppearance.AutofillCardErrorMessage"
         android:visibility="gone" />
+
+    <TextView
+        android:id="@+id/legal_message"
+        android:textAppearance="@style/TextAppearance.TextMedium.Secondary"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:visibility="gone" />
 </LinearLayout>
diff --git a/chrome/android/java/res/layout/autofill_name_fixflow.xml b/chrome/android/java/res/layout/autofill_name_fixflow.xml
index f13c46b..118fd8f 100644
--- a/chrome/android/java/res/layout/autofill_name_fixflow.xml
+++ b/chrome/android/java/res/layout/autofill_name_fixflow.xml
@@ -3,48 +3,68 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     style="@style/AlertDialogContent"
-    android:minHeight="36dp"
     android:focusable="true"
     android:focusableInTouchMode="true"
     android:layout_marginBottom="32dp"
     android:paddingBottom="16dp"
     android:layout_marginTop="6dp"
-    android:orientation="horizontal"
-    android:gravity="center_vertical">
+    android:orientation="vertical">
 
-    <com.google.android.material.textfield.TextInputLayout
-        android:id="@+id/cc_name"
-        android:labelFor="@+id/cc_name_edit"
-        android:layout_width="0dp"
-        android:layout_weight="1"
-        android:layout_height="wrap_content">
-
-        <!-- TODO(crbug.com/900912): Fix and remove lint ignore -->
-        <EditText
-            tools:ignore="Autofill,LabelFor"
-            android:id="@+id/cc_name_edit"
-            android:hint="@string/autofill_card_holder_name"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:imeOptions="actionDone"
-            android:imeActionLabel="@string/autofill_fix_flow_prompt_save_card_label"
-            android:inputType="textCapWords"/>
-
-    </com.google.android.material.textfield.TextInputLayout>
-
-    <ImageView
-        android:id="@+id/cc_name_tooltip_icon"
-        android:layout_width="wrap_content"
+    <include
+        layout="@layout/autofill_cc_details"
+        android:visibility="gone"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="6dp"
-        android:src="@drawable/btn_info"
-        android:layout_gravity="center"
-        android:contentDescription="@string/learn_more" />
+        android:layout_marginBottom="20dp" />
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="36dp"
+        android:gravity="center_vertical">
+
+        <com.google.android.material.textfield.TextInputLayout
+            android:id="@+id/cc_name"
+            android:labelFor="@+id/cc_name_edit"
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:layout_height="wrap_content">
+
+            <!-- TODO(crbug.com/900912): Fix and remove lint ignore -->
+            <EditText
+                tools:ignore="Autofill,LabelFor"
+                android:id="@+id/cc_name_edit"
+                android:hint="@string/autofill_card_holder_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:imeOptions="actionDone"
+                android:imeActionLabel="@string/autofill_fix_flow_prompt_save_card_label"
+                android:inputType="textCapWords"/>
+
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <ImageView
+            android:id="@+id/cc_name_tooltip_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="6dp"
+            android:src="@drawable/btn_info"
+            android:layout_gravity="center"
+            android:contentDescription="@string/learn_more" />
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/legal_message"
+        android:textAppearance="@style/TextAppearance.TextMedium.Secondary"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:visibility="gone" />
 
 </LinearLayout>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowBridge.java
index 6259d2a6..7e50ed2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowBridge.java
@@ -67,6 +67,10 @@
                 AutofillExpirationDateFixFlowBridge.this);
     }
 
+    /* no-op. Legal lines aren't set. */
+    @Override
+    public void onLinkClicked(String url) {}
+
     /**
      * Shows a prompt for expiration date fix flow.
      */
@@ -80,8 +84,9 @@
             return;
         }
 
-        mExpirationDateFixFlowPrompt = new AutofillExpirationDateFixFlowPrompt(
-                activity, this, mTitle, mConfirmButtonLabel, mIconId, mCardLabel);
+        mExpirationDateFixFlowPrompt =
+                AutofillExpirationDateFixFlowPrompt.createAsInfobarFixFlowPrompt(
+                        activity, this, mTitle, mConfirmButtonLabel, mIconId, mCardLabel);
         mExpirationDateFixFlowPrompt.show(activity, windowAndroid.getModalDialogManager());
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowPrompt.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowPrompt.java
index 9ab1579..a040597f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowPrompt.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowPrompt.java
@@ -7,7 +7,11 @@
 import android.app.Activity;
 import android.content.Context;
 import android.text.Editable;
+import android.text.SpannableString;
+import android.text.Spanned;
 import android.text.TextWatcher;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.EditText;
@@ -51,6 +55,47 @@
          * prompt or it was dismissed by native code.
          */
         void onUserDismiss();
+
+        /**
+         * Called when link in legal lines is clicked.
+         */
+        void onLinkClicked(String url);
+    }
+
+    /**
+     * Create a prompt dialog for the use of infobar. This dialog does not include legal lines.
+     *
+     * @param context The current context.
+     * @param delegate A {@link AutofillExpirationDateFixFlowPromptDelegate} to handle events.
+     * @param title Title of the dialog prompt.
+     * @param confirmButtonLabel Label for the confirm button.
+     * @param cardLabel Label representing a card which will be saved.
+     * @return The prompt to confirm expiration data.
+     */
+    public static AutofillExpirationDateFixFlowPrompt createAsInfobarFixFlowPrompt(Context context,
+            AutofillExpirationDateFixFlowPromptDelegate delegate, String title,
+            String confirmButtonLabel, int drawableId, String cardLabel) {
+        return new AutofillExpirationDateFixFlowPrompt(
+                context, delegate, title, confirmButtonLabel, drawableId, cardLabel, false);
+    }
+
+    /**
+     * Create a dialog prompt for the use of message. This dialog prompt includes legal lines.
+     *
+     * @param context The current context.
+     * @param delegate A {@link AutofillExpirationDateFixFlowPromptDelegate} to handle events.
+     * @param month Default value for month field. Empty string for users to fill in.
+     * @param year Default value for year field. Empty string for users to fill in.
+     * @param title Title of the dialog prompt.
+     * @param confirmButtonLabel Label for the confirm button.
+     * @param cardLabel Label representing a card which will be saved.
+     * @return The prompt to confirm expiration data.
+     */
+    public static AutofillExpirationDateFixFlowPrompt createAsMessageFixFlowPrompt(Context context,
+            AutofillExpirationDateFixFlowPromptDelegate delegate, String month, String year,
+            String title, String confirmButtonLabel, String cardLabel) {
+        return new AutofillExpirationDateFixFlowPrompt(
+                context, delegate, month, year, title, confirmButtonLabel, cardLabel);
     }
 
     private final AutofillExpirationDateFixFlowPromptDelegate mDelegate;
@@ -71,15 +116,18 @@
     /**
      * Fix flow prompt to confirm expiration date before saving the card to Google.
      */
-    public AutofillExpirationDateFixFlowPrompt(Context context,
+    private AutofillExpirationDateFixFlowPrompt(Context context,
             AutofillExpirationDateFixFlowPromptDelegate delegate, String title,
-            String confirmButtonLabel, int drawableId, String cardLabel) {
+            String confirmButtonLabel, int drawableId, String cardLabel,
+            boolean filledConfirmButton) {
         mDelegate = delegate;
         LayoutInflater inflater = LayoutInflater.from(context);
         mDialogView = inflater.inflate(R.layout.autofill_expiration_date_fix_flow, null);
         mErrorMessage = (TextView) mDialogView.findViewById(R.id.error_message);
         mCardDetailsMasked = (TextView) mDialogView.findViewById(R.id.cc_details_masked);
         mCardDetailsMasked.setText(cardLabel);
+        mDialogView.findViewById(R.id.message_divider).setVisibility(View.GONE);
+        mDialogView.findViewById(R.id.google_pay_logo).setVisibility(View.GONE);
 
         mMonthInput = (EditText) mDialogView.findViewById(R.id.cc_month_edit);
         mMonthInput.addTextChangedListener(this);
@@ -102,11 +150,24 @@
                         .with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, context.getResources(),
                                 R.string.cancel)
                         .with(ModalDialogProperties.CANCEL_ON_TOUCH_OUTSIDE, false)
-                        .with(ModalDialogProperties.POSITIVE_BUTTON_DISABLED, true);
+                        .with(ModalDialogProperties.POSITIVE_BUTTON_DISABLED, true)
+                        .with(ModalDialogProperties.PRIMARY_BUTTON_FILLED, filledConfirmButton);
         if (drawableId != 0) {
             builder.with(ModalDialogProperties.TITLE_ICON, context, drawableId);
         }
         mDialogModel = builder.build();
+        mContext = context;
+    }
+
+    private AutofillExpirationDateFixFlowPrompt(Context context,
+            AutofillExpirationDateFixFlowPromptDelegate delegate, String month, String year,
+            String title, String confirmButtonLabel, String cardLabel) {
+        // Set drawable id as 0 to remove the icon on the title.
+        this(context, delegate, title, confirmButtonLabel, /*drawableId=*/0, cardLabel, true);
+        mDialogView.findViewById(R.id.message_divider).setVisibility(View.VISIBLE);
+        mDialogView.findViewById(R.id.google_pay_logo).setVisibility(View.VISIBLE);
+        mYearInput.setText(year);
+        mMonthInput.setText(month);
     }
 
     /**
@@ -123,6 +184,23 @@
         mModalDialogManager.showDialog(mDialogModel, ModalDialogManager.ModalDialogType.APP);
     }
 
+    public void setLegalMessageLine(LegalMessageLine line) {
+        SpannableString text = new SpannableString(line.text);
+        for (final LegalMessageLine.Link link : line.links) {
+            String url = link.url;
+            text.setSpan(new ClickableSpan() {
+                @Override
+                public void onClick(View view) {
+                    mDelegate.onLinkClicked(url);
+                }
+            }, link.start, link.end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+        TextView legalMessage = mDialogView.findViewById(R.id.legal_message);
+        legalMessage.setText(text);
+        legalMessage.setMovementMethod(LinkMovementMethod.getInstance());
+        legalMessage.setVisibility(View.VISIBLE);
+    }
+
     protected void dismiss(@DialogDismissalCause int dismissalCause) {
         mModalDialogManager.dismissDialog(mDialogModel, dismissalCause);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillMessageConfirmFlowBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillMessageConfirmFlowBridge.java
new file mode 100644
index 0000000..d52af19
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillMessageConfirmFlowBridge.java
@@ -0,0 +1,168 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.autofill;
+
+import android.app.Activity;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.base.task.PostTask;
+import org.chromium.chrome.browser.autofill.AutofillExpirationDateFixFlowPrompt.AutofillExpirationDateFixFlowPromptDelegate;
+import org.chromium.chrome.browser.autofill.AutofillNameFixFlowPrompt.AutofillNameFixFlowPromptDelegate;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modaldialog.DialogDismissalCause;
+
+/**
+ * Bridge for SaveCardMessageControllerAndroid to show a confirmation dialog of name or expiration
+ * date.
+ */
+@JNINamespace("autofill")
+public class AutofillMessageConfirmFlowBridge
+        implements AutofillExpirationDateFixFlowPromptDelegate, AutofillNameFixFlowPromptDelegate {
+    @Nullable
+    private AutofillNameFixFlowPrompt mCardholderNameFixFlowPrompt;
+    @Nullable
+    private AutofillExpirationDateFixFlowPrompt mExpirationDateFixFlowPrompt;
+
+    private long mNativeSaveCardMessageConfirmDelegate;
+    private final WindowAndroid mWindowAndroid;
+    private LegalMessageLine mLegalMessageLine;
+
+    private AutofillMessageConfirmFlowBridge(
+            long nativeSaveCardMessageConfirmDelegate, WindowAndroid windowAndroid) {
+        mNativeSaveCardMessageConfirmDelegate = nativeSaveCardMessageConfirmDelegate;
+        mWindowAndroid = windowAndroid;
+    }
+
+    @Override
+    public void onPromptDismissed() {
+        // In case native is destroyed before #onPromptDismissed is executed by post tasks.
+        if (mNativeSaveCardMessageConfirmDelegate == 0) {
+            return;
+        }
+        AutofillMessageConfirmFlowBridgeJni.get().promptDismissed(
+                mNativeSaveCardMessageConfirmDelegate, AutofillMessageConfirmFlowBridge.this);
+    }
+
+    @Override
+    public void onUserAccept(String name) {
+        AutofillMessageConfirmFlowBridgeJni.get().onNameConfirmed(
+                mNativeSaveCardMessageConfirmDelegate, AutofillMessageConfirmFlowBridge.this, name);
+    }
+
+    @Override
+    public void onUserAccept(String month, String year) {
+        AutofillMessageConfirmFlowBridgeJni.get().onDateConfirmed(
+                mNativeSaveCardMessageConfirmDelegate, AutofillMessageConfirmFlowBridge.this, month,
+                year);
+    }
+
+    // no-op
+    @Override
+    public void onUserDismiss() {}
+
+    @Override
+    public void onLinkClicked(String url) {
+        AutofillMessageConfirmFlowBridgeJni.get().onLegalMessageLinkClicked(
+                mNativeSaveCardMessageConfirmDelegate, AutofillMessageConfirmFlowBridge.this, url);
+    }
+
+    @CalledByNative
+    private static AutofillMessageConfirmFlowBridge create(
+            long nativeAutofillMessageConfirmDelegate, WindowAndroid windowAndroid) {
+        return new AutofillMessageConfirmFlowBridge(
+                nativeAutofillMessageConfirmDelegate, windowAndroid);
+    }
+
+    @CalledByNative
+    private void confirmDate(
+            String month, String year, String title, String confirmButtonLabel, String cardLabel) {
+        Activity activity = mWindowAndroid.getActivity().get();
+        if (activity == null) {
+            // Clean up the native counterpart. Post the dismissal to allow the native
+            // caller to finish execution before we attempt to delete it.
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::onPromptDismissed);
+            return;
+        }
+        if (mExpirationDateFixFlowPrompt == null) {
+            mExpirationDateFixFlowPrompt =
+                    AutofillExpirationDateFixFlowPrompt.createAsMessageFixFlowPrompt(
+                            activity, this, month, year, title, confirmButtonLabel, cardLabel);
+            mExpirationDateFixFlowPrompt.setLegalMessageLine(mLegalMessageLine);
+        }
+        mExpirationDateFixFlowPrompt.show(activity, mWindowAndroid.getModalDialogManager());
+    }
+
+    @CalledByNative
+    private void confirmName(
+            String title, String inferredName, String confirmButtonLabel, String cardLabel) {
+        Activity activity = mWindowAndroid.getActivity().get();
+        if (activity == null) {
+            // Clean up the native counterpart. Post the dismissal to allow the native
+            // caller to finish execution before we attempt to delete it.
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::onPromptDismissed);
+            return;
+        }
+        if (mCardholderNameFixFlowPrompt == null) {
+            mCardholderNameFixFlowPrompt = AutofillNameFixFlowPrompt.createAsMessageFixFlowPrompt(
+                    activity, this, title, inferredName, confirmButtonLabel, cardLabel);
+            mCardholderNameFixFlowPrompt.setLegalMessageLine(mLegalMessageLine);
+        }
+        mCardholderNameFixFlowPrompt.show(activity, mWindowAndroid.getModalDialogManager());
+    }
+
+    @CalledByNative
+    private void dismiss() {
+        if (mExpirationDateFixFlowPrompt != null) {
+            mExpirationDateFixFlowPrompt.dismiss(DialogDismissalCause.DISMISSED_BY_NATIVE);
+        }
+        if (mCardholderNameFixFlowPrompt != null) {
+            mCardholderNameFixFlowPrompt.dismiss(DialogDismissalCause.DISMISSED_BY_NATIVE);
+        }
+    }
+
+    @CalledByNative
+    private void nativeBridgeDestroyed() {
+        mNativeSaveCardMessageConfirmDelegate = 0;
+    }
+
+    /**
+     * Sets a line of legal message plain text to the dialog.
+     *
+     * @param text The legal message plain text.
+     */
+    @CalledByNative
+    private void setLegalMessageLine(String text) {
+        mLegalMessageLine = new LegalMessageLine(text);
+    }
+
+    /**
+     * Marks up the last added line of legal message text with a link.
+     *
+     * @param start The inclusive offset of the start of the link in the text.
+     * @param end The exclusive offset of the end of the link in the text.
+     * @param url The URL to open when the link is clicked.
+     */
+    @CalledByNative
+    private void addLinkToLastLegalMessageLine(int start, int end, String url) {
+        mLegalMessageLine.links.add(new LegalMessageLine.Link(start, end, url));
+    }
+
+    @NativeMethods
+    interface Natives {
+        void onDateConfirmed(long nativeSaveCardMessageConfirmDelegate,
+                AutofillMessageConfirmFlowBridge caller, String month, String year);
+        void promptDismissed(
+                long nativeSaveCardMessageConfirmDelegate, AutofillMessageConfirmFlowBridge caller);
+        void onNameConfirmed(long nativeSaveCardMessageConfirmDelegate,
+                AutofillMessageConfirmFlowBridge caller, String name);
+        void onLegalMessageLinkClicked(long nativeSaveCardMessageConfirmDelegate,
+                AutofillMessageConfirmFlowBridge caller, String url);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowBridge.java
index 334557b..63059e4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowBridge.java
@@ -66,12 +66,16 @@
                 mNativeCardNameFixFlowViewAndroid, AutofillNameFixFlowBridge.this, name);
     }
 
+    /* no-op. Legal lines aren't set. */
+    @Override
+    public void onLinkClicked(String url) {}
+
     /**
      * Shows a prompt for name fix flow.
      */
     @CalledByNative
     private void show(WindowAndroid windowAndroid) {
-        mNameFixFlowPrompt = new AutofillNameFixFlowPrompt(
+        mNameFixFlowPrompt = AutofillNameFixFlowPrompt.createAsInfobarFixFlowPrompt(
                 mActivity, this, mTitle, mInferredName, mConfirmButtonLabel, mIconId);
 
         if (mNameFixFlowPrompt != null) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowPrompt.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowPrompt.java
index ae19a068..3f50361 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowPrompt.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowPrompt.java
@@ -7,7 +7,12 @@
 import android.app.Activity;
 import android.content.Context;
 import android.text.Editable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
 import android.text.TextWatcher;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.inputmethod.EditorInfo;
@@ -48,6 +53,46 @@
          * @param name Card holder name.
          */
         void onUserAccept(String name);
+
+        /**
+         * Called when link in legal lines is clicked.
+         */
+        void onLinkClicked(String url);
+    }
+
+    /**
+     * Create a dialog prompt for the use of infobar. This prompt does not include legal lines.
+     *
+     * @param context The current context.
+     * @param delegate A {@link AutofillNameFixFlowPromptDelegate} to handle events.
+     * @param title Title of the prompt.
+     * @param inferredName Name inferred from the account. Empty string for user to fill in.
+     * @param confirmButtonLabel Label for the confirm button.
+     * @param drawableId Drawable id on the title.
+     * @return A {@link AutofillNameFixFlowPrompt} to confirm name.
+     */
+    public static AutofillNameFixFlowPrompt createAsInfobarFixFlowPrompt(Context context,
+            AutofillNameFixFlowPromptDelegate delegate, String title, String inferredName,
+            String confirmButtonLabel, int drawableId) {
+        return new AutofillNameFixFlowPrompt(
+                context, delegate, title, inferredName, confirmButtonLabel, drawableId, false);
+    }
+
+    /**
+     * Create a dialog prompt for the use of message. This prompt should include legal lines.
+     *
+     * @param context The current context.
+     * @param delegate A {@link AutofillNameFixFlowPromptDelegate} to handle events.
+     * @param title Title of the prompt.
+     * @param inferredName Name inferred from the account. Empty string for user to fill in.
+     * @param confirmButtonLabel Label for the confirm button.
+     * @return A {@link AutofillNameFixFlowPrompt} to confirm name.
+     */
+    public static AutofillNameFixFlowPrompt createAsMessageFixFlowPrompt(Context context,
+            AutofillNameFixFlowPromptDelegate delegate, String title, String inferredName,
+            String confirmButtonLabel, String cardLabel) {
+        return new AutofillNameFixFlowPrompt(
+                context, delegate, title, inferredName, confirmButtonLabel, cardLabel);
     }
 
     private final AutofillNameFixFlowPromptDelegate mDelegate;
@@ -64,8 +109,9 @@
     /**
      * Fix flow prompt to confirm user name before saving the card to Google.
      */
-    public AutofillNameFixFlowPrompt(Context context, AutofillNameFixFlowPromptDelegate delegate,
-            String title, String inferredName, String confirmButtonLabel, int drawableId) {
+    private AutofillNameFixFlowPrompt(Context context, AutofillNameFixFlowPromptDelegate delegate,
+            String title, String inferredName, String confirmButtonLabel, int drawableId,
+            boolean filledConfirmButton) {
         mDelegate = delegate;
         LayoutInflater inflater = LayoutInflater.from(context);
         mDialogView = inflater.inflate(R.layout.autofill_name_fixflow, null);
@@ -73,7 +119,13 @@
         mUserNameInput = (EditText) mDialogView.findViewById(R.id.cc_name_edit);
         mUserNameInput.setText(inferredName, BufferType.EDITABLE);
         mNameFixFlowTooltipIcon = (ImageView) mDialogView.findViewById(R.id.cc_name_tooltip_icon);
-        mNameFixFlowTooltipIcon.setOnClickListener((view) -> onTooltipIconClicked());
+
+        // Do not show tooltip if inferred name is empty.
+        if (TextUtils.isEmpty(inferredName)) {
+            mNameFixFlowTooltipIcon.setVisibility(View.GONE);
+        } else {
+            mNameFixFlowTooltipIcon.setOnClickListener((view) -> onTooltipIconClicked());
+        }
 
         PropertyModel.Builder builder =
                 new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
@@ -85,7 +137,8 @@
                                 R.string.cancel)
                         .with(ModalDialogProperties.CANCEL_ON_TOUCH_OUTSIDE, false)
                         .with(ModalDialogProperties.POSITIVE_BUTTON_DISABLED,
-                                inferredName.isEmpty());
+                                inferredName.isEmpty())
+                        .with(ModalDialogProperties.PRIMARY_BUTTON_FILLED, filledConfirmButton);
         if (drawableId != 0) {
             builder.with(ModalDialogProperties.TITLE_ICON, context, drawableId);
         }
@@ -104,6 +157,14 @@
         });
     }
 
+    private AutofillNameFixFlowPrompt(Context context, AutofillNameFixFlowPromptDelegate delegate,
+            String title, String inferredName, String confirmButtonLabel, String cardLabel) {
+        this(context, delegate, title, inferredName, confirmButtonLabel, /*drawableId=*/0, true);
+        mDialogView.findViewById(R.id.cc_details).setVisibility(View.VISIBLE);
+        TextView detailsMasked = mDialogView.findViewById(R.id.cc_details_masked);
+        detailsMasked.setText(cardLabel);
+    }
+
     /**
      * Show the dialog.
      *
@@ -119,6 +180,23 @@
         mUserNameInput.addTextChangedListener(this);
     }
 
+    public void setLegalMessageLine(LegalMessageLine line) {
+        SpannableString text = new SpannableString(line.text);
+        for (final LegalMessageLine.Link link : line.links) {
+            String url = link.url;
+            text.setSpan(new ClickableSpan() {
+                @Override
+                public void onClick(View view) {
+                    mDelegate.onLinkClicked(url);
+                }
+            }, link.start, link.end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+        TextView legalMessage = mDialogView.findViewById(R.id.legal_message);
+        legalMessage.setText(text);
+        legalMessage.setMovementMethod(LinkMovementMethod.getInstance());
+        legalMessage.setVisibility(View.VISIBLE);
+    }
+
     protected void dismiss(@DialogDismissalCause int dismissalCause) {
         mModalDialogManager.dismissDialog(mDialogModel, dismissalCause);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/LegalMessageLine.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/LegalMessageLine.java
new file mode 100644
index 0000000..22e7c5e
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/LegalMessageLine.java
@@ -0,0 +1,65 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.autofill;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Legal message line with links to show in the autofill ui.
+ */
+public class LegalMessageLine {
+    /**
+     * A link in the legal message line.
+     */
+    public static class Link {
+        /**
+         * The starting inclusive index of the link position in the text.
+         */
+        public int start;
+
+        /**
+         * The ending exclusive index of the link position in the text.
+         */
+        public int end;
+
+        /**
+         * The URL of the link.
+         */
+        public String url;
+
+        /**
+         * Creates a new instance of the link.
+         *
+         * @param start The starting inclusive index of the link position in the text.
+         * @param end The ending exclusive index of the link position in the text.
+         * @param url The URL of the link.
+         */
+        public Link(int start, int end, String url) {
+            this.start = start;
+            this.end = end;
+            this.url = url;
+        }
+    }
+
+    /**
+     * The plain text legal message line.
+     */
+    public String text;
+
+    /**
+     * A collection of links in the legal message line.
+     */
+    public final List<Link> links = new LinkedList<Link>();
+
+    /**
+     * Creates a new instance of the legal message line.
+     *
+     * @param text The plain text legal message.
+     */
+    public LegalMessageLine(String text) {
+        this.text = text;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabSheetContent.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabSheetContent.java
index 0fa2639..6236a8c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabSheetContent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabSheetContent.java
@@ -19,7 +19,10 @@
 import androidx.annotation.Nullable;
 
 import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.supplier.UnownedUserDataSupplier;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.share.ShareDelegate;
+import org.chromium.chrome.browser.share.ShareDelegateSupplier;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
 import org.chromium.components.browser_ui.widget.FadingShadow;
 import org.chromium.components.browser_ui.widget.FadingShadowView;
@@ -33,6 +36,7 @@
 import org.chromium.content_public.browser.RenderCoordinates;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.IntentRequestTracker;
+import org.chromium.ui.base.WindowAndroid;
 import org.chromium.url.GURL;
 
 /**
@@ -55,6 +59,8 @@
     private final Runnable mToolbarClickCallback;
     private final Runnable mCloseButtonCallback;
     private final int mToolbarHeightPx;
+    private final UnownedUserDataSupplier<ShareDelegate> mShareDelegateSupplier =
+            new ShareDelegateSupplier();
 
     private ViewGroup mToolbarView;
     private ViewGroup mSheetContentView;
@@ -103,6 +109,13 @@
             ((ViewGroup) mWebContentView.getParent()).removeView(mWebContentView);
         }
         mThinWebView.attachWebContents(mWebContents, mWebContentView, delegate);
+
+        // Initialize the supplier of {@link ShareDelegate} for the WindowAndroid used by
+        // ThinWebView.  The {@link ShareDelegate} itself is not set by design in order to leave
+        // the share feature disabled on Preview Tab.
+        WindowAndroid window = mWebContents.getTopLevelNativeWindow();
+        assert window != null;
+        mShareDelegateSupplier.attach(window.getUnownedUserDataHost());
     }
 
     /**
@@ -245,6 +258,7 @@
     @Override
     public void destroy() {
         mThinWebView.destroy();
+        mShareDelegateSupplier.destroy();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java
index 0300b04..b44b447 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java
@@ -21,8 +21,10 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.StreamUtil;
+import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
@@ -33,6 +35,7 @@
 import org.chromium.components.minidump_uploader.MinidumpUploadCallable.MinidumpUploadStatus;
 import org.chromium.components.minidump_uploader.MinidumpUploadJobService;
 import org.chromium.components.minidump_uploader.util.CrashReportingPermissionManager;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -150,7 +153,15 @@
         sharedPrefs.writeInt(ChromePreferenceKeys.LAST_SESSION_BROWSER_PID, Process.myPid());
         ApplicationStateListener appStateListener = createApplicationStateListener();
         appStateListener.onApplicationStateChange(ApplicationStatus.getStateForApplication());
-        ApplicationStatus.registerApplicationStateListener(appStateListener);
+
+        if (ThreadUtils.runningOnUiThread()) {
+            ApplicationStatus.registerApplicationStateListener(appStateListener);
+        } else {
+            PostTask.postTask(UiThreadTaskTraits.BEST_EFFORT, () -> {
+                ApplicationStatus.registerApplicationStateListener(appStateListener);
+            });
+        }
+
         if (previousPid != 0) {
             int reason = ProcessExitReasonFromSystem.getExitReason(previousPid);
             ProcessExitReasonFromSystem.recordAsEnumHistogram(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManagerImpl.java
index 2e7e00d..e92d04b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManagerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManagerImpl.java
@@ -16,6 +16,7 @@
 import static org.chromium.chrome.browser.download.DownloadNotificationService.EXTRA_IS_AUTO_RESUMPTION;
 import static org.chromium.chrome.browser.download.DownloadNotificationService.EXTRA_IS_OFF_THE_RECORD;
 import static org.chromium.chrome.browser.download.DownloadNotificationService.clearResumptionAttemptLeft;
+import static org.chromium.chrome.browser.notifications.NotificationConstants.EXTRA_NOTIFICATION_ID;
 
 import android.app.DownloadManager;
 import android.app.Service;
@@ -132,34 +133,47 @@
         if (!immediateNotificationUpdateNeeded(action)) return;
 
         final DownloadSharedPreferenceEntry entry = getDownloadEntryFromIntent(intent);
-        if (entry == null) return;
+        final ContentId contentId = getContentIdFromIntent(intent);
 
         switch (action) {
             case ACTION_DOWNLOAD_PAUSE:
-                mDownloadNotificationService.notifyDownloadPaused(entry.id, entry.fileName, true,
-                        false, entry.otrProfileID, entry.isTransient, null, null, false, true,
-                        false, PendingState.NOT_PENDING);
+                if (entry != null) {
+                    mDownloadNotificationService.notifyDownloadPaused(entry.id, entry.fileName,
+                            true, false, entry.otrProfileID, entry.isTransient, null, null, false,
+                            true, false, PendingState.NOT_PENDING);
+                }
                 break;
 
             case ACTION_DOWNLOAD_CANCEL:
-                mDownloadNotificationService.notifyDownloadCanceled(entry.id, true);
+                int notificationId = IntentUtils.safeGetIntExtra(intent, EXTRA_NOTIFICATION_ID, -1);
+                // For old build, notification needs to be retrieved from the
+                // DownloadSharedPreferenceEntry.
+                if (notificationId < 0 && entry != null) {
+                    notificationId = entry.notificationId;
+                }
+                if (notificationId >= 0 && contentId != null) {
+                    mDownloadNotificationService.notifyDownloadCanceled(
+                            contentId, notificationId, true);
+                }
                 break;
 
             case ACTION_DOWNLOAD_RESUME:
-                // If user manually resumes a download, update the network type if it
-                // is not metered previously.
-                boolean canDownloadWhileMetered = entry.canDownloadWhileMetered
-                        || DownloadManagerService.isActiveNetworkMetered(
-                                ContextUtils.getApplicationContext());
-                // Update the SharedPreference entry.
-                mDownloadSharedPreferenceHelper.addOrReplaceSharedPreferenceEntry(
-                        new DownloadSharedPreferenceEntry(entry.id, entry.notificationId,
-                                entry.otrProfileID, canDownloadWhileMetered, entry.fileName, true,
-                                entry.isTransient));
+                if (entry != null) {
+                    // If user manually resumes a download, update the network type if it
+                    // is not metered previously.
+                    boolean canDownloadWhileMetered = entry.canDownloadWhileMetered
+                            || DownloadManagerService.isActiveNetworkMetered(
+                                    ContextUtils.getApplicationContext());
+                    // Update the SharedPreference entry.
+                    mDownloadSharedPreferenceHelper.addOrReplaceSharedPreferenceEntry(
+                            new DownloadSharedPreferenceEntry(entry.id, entry.notificationId,
+                                    entry.otrProfileID, canDownloadWhileMetered, entry.fileName,
+                                    true, entry.isTransient));
 
-                mDownloadNotificationService.notifyDownloadPending(entry.id, entry.fileName,
-                        entry.otrProfileID, entry.canDownloadWhileMetered, entry.isTransient, null,
-                        null, false, true, PendingState.PENDING_NETWORK);
+                    mDownloadNotificationService.notifyDownloadPending(entry.id, entry.fileName,
+                            entry.otrProfileID, entry.canDownloadWhileMetered, entry.isTransient,
+                            null, null, false, true, PendingState.PENDING_NETWORK);
+                }
                 break;
 
             default:
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java
index 5eec0b85..81a4a47 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java
@@ -167,7 +167,8 @@
                                         .PENDING_ANOTHER_DOWNLOAD);
                         break;
                 }
-
+                cancelIntent.putExtra(NotificationConstants.EXTRA_NOTIFICATION_ID,
+                        downloadUpdate.getNotificationId());
                 builder.setOngoing(true)
                         .setPriorityBeforeO(NotificationCompat.PRIORITY_HIGH)
                         .setAutoCancel(false)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
index e940334..053d834 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
@@ -286,19 +286,31 @@
     }
 
     /**
+     * Called when a download is canceled given the notification ID.
+     * @param id The {@link ContentId} of the download.
+     * @param notificationId Notification ID of the download.
+     * @param hasUserGesture Whether cancel is triggered by user gesture.
+     */
+    @VisibleForTesting
+    public void notifyDownloadCanceled(ContentId id, int notificationId, boolean hasUserGesture) {
+        mDownloadForegroundServiceManager.updateDownloadStatus(ContextUtils.getApplicationContext(),
+                DownloadStatus.CANCELLED, notificationId, null);
+        cancelNotification(notificationId, id);
+    }
+
+    /**
      * Called when a download is canceled.  This method uses internal tracking to try to find the
      * notification id to cancel.
+     * Called when a download is canceled.
      * @param id The {@link ContentId} of the download.
+     * @param hasUserGesture Whether cancel is triggered by user gesture.
      */
     @VisibleForTesting
     public void notifyDownloadCanceled(ContentId id, boolean hasUserGesture) {
         DownloadSharedPreferenceEntry entry =
                 mDownloadSharedPreferenceHelper.getDownloadSharedPreferenceEntry(id);
         if (entry == null) return;
-
-        mDownloadForegroundServiceManager.updateDownloadStatus(ContextUtils.getApplicationContext(),
-                DownloadStatus.CANCELLED, entry.notificationId, null);
-        cancelNotification(entry.notificationId, id);
+        notifyDownloadCanceled(id, entry.notificationId, hasUserGesture);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/AutofillSaveCardInfoBar.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/AutofillSaveCardInfoBar.java
index 0da7bce..5b457c0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/AutofillSaveCardInfoBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/AutofillSaveCardInfoBar.java
@@ -20,6 +20,7 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.autofill.LegalMessageLine;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.components.browser_ui.widget.RoundedCornerImageView;
 import org.chromium.components.infobars.ConfirmInfoBar;
@@ -35,62 +36,6 @@
  * An infobar for saving credit card information.
  */
 public class AutofillSaveCardInfoBar extends ConfirmInfoBar {
-    /**
-     * Legal message line with links to show in the infobar.
-     */
-    public static class LegalMessageLine {
-        /**
-         * A link in the legal message line.
-         */
-        public static class Link {
-            /**
-             * The starting inclusive index of the link position in the text.
-             */
-            public int start;
-
-            /**
-             * The ending exclusive index of the link position in the text.
-             */
-            public int end;
-
-            /**
-             * The URL of the link.
-             */
-            public String url;
-
-            /**
-             * Creates a new instance of the link.
-             *
-             * @param start The starting inclusive index of the link position in the text.
-             * @param end The ending exclusive index of the link position in the text.
-             * @param url The URL of the link.
-             */
-            public Link(int start, int end, String url) {
-                this.start = start;
-                this.end = end;
-                this.url = url;
-            }
-        }
-
-        /**
-         * The plain text legal message line.
-         */
-        public String text;
-
-        /**
-         * A collection of links in the legal message line.
-         */
-        public final List<Link> links = new LinkedList<Link>();
-
-        /**
-         * Creates a new instance of the legal message line.
-         *
-         * @param text The plain text legal message.
-         */
-        public LegalMessageLine(String text) {
-            this.text = text;
-        }
-    }
 
     private final @Nullable String mAccountFooterEmail;
     private final @Nullable Bitmap mAccountFooterAvatar;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/TranslateCompactInfoBar.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/TranslateCompactInfoBar.java
index 866ba8f..863f58e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/TranslateCompactInfoBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/TranslateCompactInfoBar.java
@@ -102,10 +102,15 @@
 
     private final PrefChangeRegistrar mPrefChangeRegistrar;
 
-    /** The controller for translate UI snackbars. */
+    /**
+     * The controller for translate UI snackbars. The snackbars show when a translate preference has
+     * been enabled. The |onAction| method cancels the action, while onDismissNoAction toggles the
+     * preference.
+     */
     class TranslateSnackbarController implements SnackbarController {
         private final int mActionId;
 
+        /** @param actionId Overflow menu action id for translate preference to enable.*/
         public TranslateSnackbarController(int actionId) {
             mActionId = actionId;
         }
@@ -113,7 +118,7 @@
         @Override
         public void onDismissNoAction(Object actionData) {
             mSnackbarController = null;
-            handleTranslateOptionPostSnackbar(mActionId);
+            handleTranslateOverflowOption(mActionId);
         }
 
         @Override
@@ -147,20 +152,25 @@
 
     @CalledByNative
     private static InfoBar create(Tab tab, int initialStep, String sourceLanguageCode,
-            String targetLanguageCode, boolean alwaysTranslate, boolean triggeredFromMenu,
-            String[] allLanguages, String[] allLanguagesCodes, int[] allLanguagesHashCodes,
-            String[] contentLanguagesCodes, int tabTextColor) {
+            String targetLanguageCode,
+            boolean neverLanguage /* Never automatically translate the source language */,
+            boolean neverDomain /* Never automatically translate the domain */,
+            boolean alwaysTranslate /* Always automatically translate the source language */,
+            boolean triggeredFromMenu, String[] allLanguages, String[] allLanguagesCodes,
+            int[] allLanguagesHashCodes, String[] contentLanguagesCodes, int tabTextColor) {
         recordInfobarAction(InfobarEvent.INFOBAR_IMPRESSION);
 
         return new TranslateCompactInfoBar(tab.getWindowAndroid(), initialStep, sourceLanguageCode,
-                targetLanguageCode, alwaysTranslate, triggeredFromMenu, allLanguages,
-                allLanguagesCodes, allLanguagesHashCodes, contentLanguagesCodes, tabTextColor);
+                targetLanguageCode, neverLanguage, neverDomain, alwaysTranslate, triggeredFromMenu,
+                allLanguages, allLanguagesCodes, allLanguagesHashCodes, contentLanguagesCodes,
+                tabTextColor);
     }
 
     TranslateCompactInfoBar(WindowAndroid windowAndroid, int initialStep, String sourceLanguageCode,
-            String targetLanguageCode, boolean alwaysTranslate, boolean triggeredFromMenu,
-            String[] allLanguages, String[] allLanguagesCodes, int[] allLanguagesHashCodes,
-            String[] contentLanguagesCodes, int tabTextColor) {
+            String targetLanguageCode, boolean neverLanguage, boolean neverDomain,
+            boolean alwaysTranslate, boolean triggeredFromMenu, String[] allLanguages,
+            String[] allLanguagesCodes, int[] allLanguagesHashCodes, String[] contentLanguagesCodes,
+            int tabTextColor) {
         super(R.drawable.infobar_translate_compact, 0, null, null);
         mWindowAndroid = windowAndroid;
 
@@ -174,8 +184,8 @@
         mInitialStep = initialStep;
         mDefaultTextColor = tabTextColor;
         mOptions = TranslateOptions.create(sourceLanguageCode, targetLanguageCode, allLanguages,
-                allLanguagesCodes, alwaysTranslate, triggeredFromMenu, allLanguagesHashCodes,
-                contentLanguagesCodes);
+                allLanguagesCodes, neverLanguage, neverDomain, alwaysTranslate, triggeredFromMenu,
+                allLanguagesHashCodes, contentLanguagesCodes);
     }
 
     @Override
@@ -462,23 +472,35 @@
                             Snackbar.UMA_TRANSLATE_ALWAYS, ACTION_OVERFLOW_ALWAYS_TRANSLATE);
                 } else {
                     recordInfobarAction(InfobarEvent.INFOBAR_ALWAYS_TRANSLATE_UNDO);
-                    handleTranslateOptionPostSnackbar(ACTION_OVERFLOW_ALWAYS_TRANSLATE);
+                    handleTranslateOverflowOption(ACTION_OVERFLOW_ALWAYS_TRANSLATE);
                 }
                 return;
             case TranslateMenu.ID_OVERFLOW_NEVER_LANGUAGE:
-                recordInfobarAction(InfobarEvent.INFOBAR_NEVER_TRANSLATE);
-                recordInfobarLanguageData(
-                        INFOBAR_HISTOGRAM_NEVER_TRANSLATE_LANGUAGE, mOptions.sourceLanguageCode());
-                createAndShowSnackbar(
-                        getContext().getString(R.string.translate_snackbar_language_never,
-                                mOptions.sourceLanguageName()),
-                        Snackbar.UMA_TRANSLATE_NEVER, ACTION_OVERFLOW_NEVER_LANGUAGE);
+                // Only show snackbar when "Never Translate" is enabled.
+                if (!mOptions.getTranslateState(TranslateOptions.Type.NEVER_LANGUAGE)) {
+                    recordInfobarAction(InfobarEvent.INFOBAR_NEVER_TRANSLATE);
+                    recordInfobarLanguageData(INFOBAR_HISTOGRAM_NEVER_TRANSLATE_LANGUAGE,
+                            mOptions.sourceLanguageCode());
+                    createAndShowSnackbar(
+                            getContext().getString(R.string.translate_snackbar_language_never,
+                                    mOptions.sourceLanguageName()),
+                            Snackbar.UMA_TRANSLATE_NEVER, ACTION_OVERFLOW_NEVER_LANGUAGE);
+                } else {
+                    recordInfobarAction(InfobarEvent.INFOBAR_NEVER_TRANSLATE_UNDO);
+                    handleTranslateOverflowOption(ACTION_OVERFLOW_NEVER_LANGUAGE);
+                }
                 return;
             case TranslateMenu.ID_OVERFLOW_NEVER_SITE:
-                recordInfobarAction(InfobarEvent.INFOBAR_NEVER_TRANSLATE_SITE);
-                createAndShowSnackbar(
-                        getContext().getString(R.string.translate_snackbar_site_never),
-                        Snackbar.UMA_TRANSLATE_NEVER_SITE, ACTION_OVERFLOW_NEVER_SITE);
+                // Only show snackbar when "Never Translate" is enabled.
+                if (!mOptions.getTranslateState(TranslateOptions.Type.NEVER_DOMAIN)) {
+                    recordInfobarAction(InfobarEvent.INFOBAR_NEVER_TRANSLATE_SITE);
+                    createAndShowSnackbar(
+                            getContext().getString(R.string.translate_snackbar_site_never),
+                            Snackbar.UMA_TRANSLATE_NEVER_SITE, ACTION_OVERFLOW_NEVER_SITE);
+                } else {
+                    recordInfobarAction(InfobarEvent.INFOBAR_NEVER_TRANSLATE_SITE_UNDO);
+                    handleTranslateOverflowOption(ACTION_OVERFLOW_NEVER_SITE);
+                }
                 return;
             case TranslateMenu.ID_OVERFLOW_NOT_THIS_LANGUAGE:
                 recordInfobarAction(InfobarEvent.INFOBAR_PAGE_NOT_IN);
@@ -585,7 +607,7 @@
     private void createAndShowSnackbar(String title, int umaType, int actionId) {
         if (getSnackbarManager() == null) {
             // Directly apply menu option, if snackbar system is not working.
-            handleTranslateOptionPostSnackbar(actionId);
+            handleTranslateOverflowOption(actionId);
             return;
         }
         switch (actionId) {
@@ -620,7 +642,7 @@
         return SnackbarManagerProvider.from(mWindowAndroid);
     }
 
-    private void handleTranslateOptionPostSnackbar(int actionId) {
+    private void handleTranslateOverflowOption(int actionId) {
         // Quit if native is destroyed.
         if (mNativeTranslateInfoBarPtr == 0) return;
 
@@ -642,17 +664,25 @@
                 mUserInteracted = true;
                 // Fallthrough intentional.
             case ACTION_AUTO_NEVER_LANGUAGE:
-                // After applying this option, the infobar will dismiss.
+                mOptions.toggleNeverTranslateLanguageState(
+                        !mOptions.getTranslateState(TranslateOptions.Type.NEVER_LANGUAGE));
+                // If toggling never translate to true, after applying this option the infobar will
+                // dismiss.
                 TranslateCompactInfoBarJni.get().applyBoolTranslateOption(
                         mNativeTranslateInfoBarPtr, TranslateCompactInfoBar.this,
-                        TranslateOption.NEVER_TRANSLATE, true);
+                        TranslateOption.NEVER_TRANSLATE,
+                        mOptions.getTranslateState(TranslateOptions.Type.NEVER_LANGUAGE));
                 return;
             case ACTION_OVERFLOW_NEVER_SITE:
+                mOptions.toggleNeverTranslateDomainState(
+                        !mOptions.getTranslateState(TranslateOptions.Type.NEVER_DOMAIN));
                 mUserInteracted = true;
-                // After applying this option, the infobar will dismiss.
+                // If toggling never translate to true, after applying this option the infobar will
+                // dismiss.
                 TranslateCompactInfoBarJni.get().applyBoolTranslateOption(
                         mNativeTranslateInfoBarPtr, TranslateCompactInfoBar.this,
-                        TranslateOption.NEVER_TRANSLATE_SITE, true);
+                        TranslateOption.NEVER_TRANSLATE_SITE,
+                        mOptions.getTranslateState(TranslateOptions.Type.NEVER_DOMAIN));
                 return;
             default:
                 assert false : "Unsupported Menu Item Id, in handle post snackbar";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetrics.java
index 580fa6a..bdb8929 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetrics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetrics.java
@@ -23,13 +23,7 @@
 
     private static long sTimeoutDurationMs = TIMEOUT_DURATION_MS;
     private static boolean sLoggedLaunchBehavior;
-    static {
-        ApplicationStatus.registerApplicationStateListener(newState -> {
-            if (newState == ApplicationState.HAS_STOPPED_ACTIVITIES) {
-                sLoggedLaunchBehavior = false;
-            }
-        });
-    }
+    private static boolean sHasRegisteredApplicationStateListener;
 
     // Constants used to log UMA "enum" histogram about launch type.
     private static final int LAUNCH_FROM_ICON = 0;
@@ -45,6 +39,16 @@
         mLogLaunchRunnable = () -> logLaunchBehavior(false);
     }
 
+    private void ensureApplicationStateListenerRegistered() {
+        if (sHasRegisteredApplicationStateListener) return;
+        sHasRegisteredApplicationStateListener = true;
+        ApplicationStatus.registerApplicationStateListener(newState -> {
+            if (newState == ApplicationState.HAS_STOPPED_ACTIVITIES) {
+                sLoggedLaunchBehavior = false;
+            }
+        });
+    }
+
     /**
      * Signal that an intent with ACTION_MAIN was received.
      *
@@ -78,11 +82,13 @@
      * and the type of each launch.
      */
     public void logLaunchBehavior() {
+        ensureApplicationStateListenerRegistered();
         if (sLoggedLaunchBehavior) return;
         ThreadUtils.getUiThreadHandler().postDelayed(mLogLaunchRunnable, sTimeoutDurationMs);
     }
 
     private void logLaunchBehavior(boolean isLaunchFromIcon) {
+        ensureApplicationStateListenerRegistered();
         if (sLoggedLaunchBehavior) return;
         sLoggedLaunchBehavior = true;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPage.java
index 44c87542..725f4fe 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPage.java
@@ -117,7 +117,7 @@
             }
         };
 
-        mTitle = host.getContext().getResources().getString(R.string.button_new_tab);
+        mTitle = host.getContext().getResources().getString(R.string.new_incognito_tab_title);
 
         LayoutInflater inflater = LayoutInflater.from(host.getContext());
         mIncognitoNewTabPageView =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
index a21feb0..2ace16fa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
@@ -329,7 +329,7 @@
                 activity, profile, navigationDelegate, snackbarManager);
 
         mResources = activity.getResources();
-        mTitle = activity.getResources().getString(R.string.button_new_tab);
+        mTitle = activity.getResources().getString(R.string.new_tab_title);
         mBackgroundColor =
                 ApiCompatibilityUtils.getColor(activity.getResources(), R.color.default_bg_color);
         mIsTablet = isTablet;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java
index 0ba407b3..9e924e2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java
@@ -71,6 +71,11 @@
         protected void removeObserverFromTabProvider() {
             TestThreadUtils.runOnUiThreadBlocking(super::removeObserverFromTabProvider);
         }
+
+        @Override
+        public void destroy() {
+            TestThreadUtils.runOnUiThreadBlocking(super::destroy);
+        }
     }
 
     @Rule
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java
index 0ce98327..da247c1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java
@@ -38,6 +38,7 @@
 import org.chromium.content_public.browser.ViewEventSink;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
 import org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.content_public.browser.test.util.WebContentsUtils;
@@ -213,14 +214,15 @@
         mActivityTestRule.startMainActivityOnBlankPage();
         final WebContents webContents = mActivityTestRule.getWebContents();
         final CallbackHelper onTitleUpdatedHelper = new CallbackHelper();
-        final WebContentsObserver observer =
-                new WebContentsObserver(webContents) {
-                    @Override
-                    public void titleWasSet(String title) {
-                        mTitle = title;
-                        onTitleUpdatedHelper.notifyCalled();
-                    }
-                };
+        final WebContentsObserver observer = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            return new WebContentsObserver(webContents) {
+                @Override
+                public void titleWasSet(String title) {
+                    mTitle = title;
+                    onTitleUpdatedHelper.notifyCalled();
+                }
+            };
+        });
         int callCount = onTitleUpdatedHelper.getCallCount();
         String url = UrlUtils.getIsolatedTestFileUrl(
                 "chrome/test/data/android/content_view_focus/content_view_blur_focus.html");
@@ -236,6 +238,7 @@
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> eventSink.onResumeForTesting());
         onTitleUpdatedHelper.waitForCallback(callCount);
         Assert.assertEquals("focused", mTitle);
-        mActivityTestRule.getWebContents().removeObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getWebContents().removeObserver(observer));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsTest.java
index dde3c8f..9e9846f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsTest.java
@@ -59,6 +59,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeApplicationTestUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.network.mojom.ReferrerPolicy;
 import org.chromium.ui.mojom.WindowOpenDisposition;
 import org.chromium.url.GURL;
@@ -135,7 +136,8 @@
                         }
                     };
                 };
-        ApplicationStatus.registerStateListenerForAllActivities(listener);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> ApplicationStatus.registerStateListenerForAllActivities(listener));
 
         final int mainCount =
                 histogramCountForValue(LaunchCauseMetrics.LaunchCause.MAIN_LAUNCHER_ICON);
@@ -148,7 +150,8 @@
         });
         Assert.assertEquals(mainCount,
                 histogramCountForValue(LaunchCauseMetrics.LaunchCause.MAIN_LAUNCHER_ICON));
-        ApplicationStatus.unregisterActivityStateListener(listener);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> ApplicationStatus.unregisterActivityStateListener(listener));
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/attribution_reporting/AttributionIntentIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/attribution_reporting/AttributionIntentIntegrationTest.java
index 13b557d..e2dc3163 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/attribution_reporting/AttributionIntentIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/attribution_reporting/AttributionIntentIntegrationTest.java
@@ -51,6 +51,7 @@
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.content_public.browser.NavigationHandle;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Tests attribution reporting intents.
@@ -108,13 +109,17 @@
                 };
             }
         };
-        ApplicationStatus.registerStateListenerForAllActivities(mActivityStateListener);
+        TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> ApplicationStatus.registerStateListenerForAllActivities(
+                                mActivityStateListener));
     }
 
     @After
     public void tearDown() {
         ContextUtils.getApplicationContext().unregisterReceiver(mReceiver);
-        ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> ApplicationStatus.unregisterActivityStateListener(mActivityStateListener));
     }
 
     private Intent makeValidAttributionIntent(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkEditTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkEditTest.java
index fee8896..c63f70e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkEditTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkEditTest.java
@@ -93,18 +93,23 @@
                 mModelChangedCallback.notifyCalled();
             }
         };
-        mBookmarkModel.addObserver(mModelObserver);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mBookmarkModel.addObserver(mModelObserver));
 
         startEditActivity(mBookmarkId);
-        ApplicationStatus.registerStateListenerForActivity(
-                mActivityStateListener, mBookmarkEditActivity);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            ApplicationStatus.registerStateListenerForActivity(
+                    mActivityStateListener, mBookmarkEditActivity);
+        });
     }
 
     @After
     public void tearDown() {
-        mBookmarkModel.removeObserver(mModelObserver);
-        TestThreadUtils.runOnUiThreadBlocking(() -> mBookmarkModel.removeAllUserBookmarks());
-        ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mBookmarkModel.removeObserver(mModelObserver);
+            mBookmarkModel.removeAllUserBookmarks();
+            ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
+        });
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuRenderTest.java
index e361266..12ff666 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuRenderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuRenderTest.java
@@ -64,9 +64,10 @@
     @Override
     public void setUpTest() throws Exception {
         super.setUpTest();
-        mListItems = new ModelList();
-        mAdapter = new ModelListAdapter(mListItems);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mListItems = new ModelList();
+            mAdapter = new ModelListAdapter(mListItems);
+
             getActivity().setContentView(R.layout.context_menu_fullscreen_container);
             mView = getActivity().findViewById(android.R.id.content);
             ((ViewStub) mView.findViewById(R.id.context_menu_stub)).inflate();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java
index fba6eb1..26e5e41 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java
@@ -233,20 +233,23 @@
 
         final CallbackHelper newTabCallback = new CallbackHelper();
         final AtomicReference<Tab> newTab = new AtomicReference<>();
-        mDownloadTestRule.getActivity().getTabModelSelector().addObserver(
-                new TabModelSelectorObserver() {
-                    @Override
-                    public void onNewTabCreated(Tab tab, @TabCreationState int creationState) {
-                        if (CriticalPersistedTabData.from(tab).getParentId()
-                                != activityTab.getId()) {
-                            return;
-                        }
-                        newTab.set(tab);
-                        newTabCallback.notifyCalled();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mDownloadTestRule.getActivity().getTabModelSelector().addObserver(
+                    new TabModelSelectorObserver() {
+                        @Override
+                        public void onNewTabCreated(Tab tab, @TabCreationState int creationState) {
+                            if (CriticalPersistedTabData.from(tab).getParentId()
+                                    != activityTab.getId()) {
+                                return;
+                            }
+                            newTab.set(tab);
+                            newTabCallback.notifyCalled();
 
-                        mDownloadTestRule.getActivity().getTabModelSelector().removeObserver(this);
-                    }
-                });
+                            mDownloadTestRule.getActivity().getTabModelSelector().removeObserver(
+                                    this);
+                        }
+                    });
+        });
 
         int callbackCount = newTabCallback.getCallCount();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
index b16d1a02..0c263b4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
@@ -2087,7 +2087,8 @@
                 tabCreatedHelper.notifyCalled();
             }
         };
-        sActivityTestRule.getActivity().getTabModelSelector().addObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> sActivityTestRule.getActivity().getTabModelSelector().addObserver(observer));
         // Track User Actions
         mActionTester = new UserActionTester();
 
@@ -2119,7 +2120,9 @@
         assertUserActionRecorded("ContextualSearch.TabPromotion");
 
         // -------- CLEAN UP ---------
-        sActivityTestRule.getActivity().getTabModelSelector().removeObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            sActivityTestRule.getActivity().getTabModelSelector().removeObserver(observer);
+        });
     }
 
     //============================================================================================
@@ -2488,7 +2491,7 @@
     public void testNotifyObserversAfterNonResolve(@EnabledFeature int enabledFeature)
             throws Exception {
         TestContextualSearchObserver observer = new TestContextualSearchObserver();
-        mManager.addObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mManager.addObserver(observer));
         triggerNonResolve("states");
         Assert.assertEquals(1, observer.getShowCount());
         Assert.assertEquals(0, observer.getHideCount());
@@ -2496,7 +2499,7 @@
         tapBasePageToClosePanel();
         Assert.assertEquals(1, observer.getShowCount());
         Assert.assertEquals(1, observer.getHideCount());
-        mManager.removeObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mManager.removeObserver(observer));
     }
 
     /**
@@ -2514,7 +2517,7 @@
         // Mark the user undecided so we won't allow sending surroundings.
         mPolicy.overrideDecidedStateForTesting(false);
         TestContextualSearchObserver observer = new TestContextualSearchObserver();
-        mManager.addObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mManager.addObserver(observer));
         triggerNonResolve("states");
         Assert.assertEquals(1, observer.getShowRedactedCount());
         Assert.assertEquals(1, observer.getShowCount());
@@ -2524,7 +2527,7 @@
         Assert.assertEquals(1, observer.getShowRedactedCount());
         Assert.assertEquals(1, observer.getShowCount());
         Assert.assertEquals(1, observer.getHideCount());
-        mManager.removeObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mManager.removeObserver(observer));
     }
 
     /**
@@ -2539,7 +2542,7 @@
     public void testNotifyObserversAfterResolve(@EnabledFeature int enabledFeature)
             throws Exception {
         TestContextualSearchObserver observer = new TestContextualSearchObserver();
-        mManager.addObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mManager.addObserver(observer));
         simulateResolveSearch("states");
         Assert.assertEquals(1, observer.getShowCount());
         Assert.assertEquals(0, observer.getHideCount());
@@ -2547,7 +2550,7 @@
         tapBasePageToClosePanel();
         Assert.assertEquals(1, observer.getShowCount());
         Assert.assertEquals(1, observer.getHideCount());
-        mManager.removeObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mManager.removeObserver(observer));
     }
 
     /**
@@ -2577,7 +2580,7 @@
     public void testNotifyObserversOnClearSelectionAfterLongpress(
             @EnabledFeature int enabledFeature) throws Exception {
         TestContextualSearchObserver observer = new TestContextualSearchObserver();
-        mManager.addObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mManager.addObserver(observer));
         longPressNode("states");
         Assert.assertEquals(0, observer.getHideCount());
 
@@ -2590,7 +2593,7 @@
         waitForPanelToClose();
         Assert.assertEquals(1, observer.getShowCount());
         Assert.assertEquals(1, observer.getHideCount());
-        mManager.removeObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mManager.removeObserver(observer));
     }
 
     /**
@@ -3726,7 +3729,7 @@
             throws Exception {
         mPolicy.overrideDecidedStateForTesting(true);
         TestContextualSearchObserver observer = new TestContextualSearchObserver();
-        mManager.addObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mManager.addObserver(observer));
 
         simulateSlowResolveSearch("states");
         simulateSlowResolveFinished();
@@ -3736,7 +3739,7 @@
         Assert.assertEquals("United States".length(), observer.getLastShownLength());
         Assert.assertEquals(2, observer.getShowCount());
         Assert.assertEquals(1, observer.getHideCount());
-        mManager.removeObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mManager.removeObserver(observer));
     }
 
     /** Asserts that the given value is either 1 or 2.  Helpful for flaky tests. */
@@ -3755,7 +3758,7 @@
         FeatureList.setTestFeatures(ENABLE_NONE);
 
         TestContextualSearchObserver observer = new TestContextualSearchObserver();
-        mManager.addObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mManager.addObserver(observer));
 
         clickWordNode("search");
         Assert.assertEquals(1, observer.getShowCount());
@@ -3769,7 +3772,7 @@
         // tests.  See crbug.com/776541.
         assertValueIs1or2(observer.getShowCount());
         Assert.assertEquals(1, observer.getHideCount());
-        mManager.removeObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mManager.removeObserver(observer));
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/crypto/CipherFactoryTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/crypto/CipherFactoryTest.java
index 7771d8f..c1248e6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/crypto/CipherFactoryTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/crypto/CipherFactoryTest.java
@@ -16,7 +16,6 @@
 import org.chromium.base.ByteArrayGenerator;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
-import org.chromium.chrome.browser.crypto.CipherFactory.CipherDataObserver;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.Arrays;
@@ -57,16 +56,16 @@
         }
     }
 
-    /** A test implementation to verify observer is correctly notified. */
-    private static class TestCipherDataObserver implements CipherDataObserver {
+    /** A test implementation to verify callback is correctly notified. */
+    private static class TestCipherDataCallback implements Runnable {
         private int mDataObserverNotifiedCount;
 
         @Override
-        public void onCipherDataGenerated() {
+        public void run() {
             mDataObserverNotifiedCount++;
         }
 
-        /** Whether the cipher data observer has been notified that cipher data is generated. */
+        /** Whether the cipher data callback has been notified that cipher data is generated. */
         public int getTimesNotified() {
             return mDataObserverNotifiedCount;
         }
@@ -257,43 +256,44 @@
     }
 
     /**
-     * Checks that an observer is notified when cipher data is created.
+     * Checks that an callback is notified when cipher data is created.
      */
     @Test
     @MediumTest
-    public void testCipherFactoryObserver() {
-        TestCipherDataObserver observer = new TestCipherDataObserver();
-        CipherFactory.getInstance().addCipherDataObserver(observer);
-        Assert.assertEquals(0, observer.getTimesNotified());
+    public void testCipherFactoryCallback() {
+        TestCipherDataCallback callback = new TestCipherDataCallback();
+        CipherFactory.getInstance().setTestCipherDataGeneratedCallback(callback);
+        Assert.assertEquals(0, callback.getTimesNotified());
         CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE);
         TestThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
-        Assert.assertEquals(1, observer.getTimesNotified());
+        Assert.assertEquals(1, callback.getTimesNotified());
         CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE);
         TestThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
-        Assert.assertEquals(1, observer.getTimesNotified());
+        Assert.assertEquals(1, callback.getTimesNotified());
         CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE);
         TestThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
-        Assert.assertEquals(1, observer.getTimesNotified());
-        CipherFactory.getInstance().removeCipherDataObserver(observer);
+        Assert.assertEquals(1, callback.getTimesNotified());
+        CipherFactory.getInstance().setTestCipherDataGeneratedCallback(null);
     }
 
     /**
-     * Verifies that if the observer is attached after cipher data has already been
-     * created the observer doesn't fire.
+     * Verifies that if the callback is attached after cipher data has already been
+     * created the callback doesn't fire.
      */
     @Test
     @MediumTest
-    public void testCipherFactoryObserverTooLate() {
+    public void testCipherFactoryCallbackTooLate() {
         CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE);
         // Ensures that cipher finishes initializing before running the rest of the test.
         TestThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
-        TestCipherDataObserver observer = new TestCipherDataObserver();
-        CipherFactory.getInstance().addCipherDataObserver(observer);
+        TestCipherDataCallback callback = new TestCipherDataCallback();
+        CipherFactory.getInstance().setTestCipherDataGeneratedCallback(callback);
         TestThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
-        Assert.assertEquals(0, observer.getTimesNotified());
+        Assert.assertEquals(0, callback.getTimesNotified());
         CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE);
         TestThreadUtils.runOnUiThreadBlocking(mEmptyRunnable);
-        Assert.assertEquals(0, observer.getTimesNotified());
+        Assert.assertEquals(0, callback.getTimesNotified());
+        CipherFactory.getInstance().setTestCipherDataGeneratedCallback(null);
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index 9fac08bf..cf236b2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -293,14 +293,16 @@
 
         final Tab tab = getActivity().getActivityTab();
         final CallbackHelper pageLoadFinishedHelper = new CallbackHelper();
-        tab.addObserver(new EmptyTabObserver() {
-            @Override
-            public void onLoadUrl(Tab tab, LoadUrlParams params, int loadType) {
-                assertTrue(params.getVerbatimHeaders().contains("bearer-token: Some token"));
-                assertTrue(params.getVerbatimHeaders().contains(
-                        "redirect-url: https://www.google.com"));
-                pageLoadFinishedHelper.notifyCalled();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.addObserver(new EmptyTabObserver() {
+                @Override
+                public void onLoadUrl(Tab tab, LoadUrlParams params, int loadType) {
+                    assertTrue(params.getVerbatimHeaders().contains("bearer-token: Some token"));
+                    assertTrue(params.getVerbatimHeaders().contains(
+                            "redirect-url: https://www.google.com"));
+                    pageLoadFinishedHelper.notifyCalled();
+                }
+            });
         });
 
         TestThreadUtils.runOnUiThreadBlockingNoException(
@@ -855,11 +857,13 @@
                 }));
         final Tab tab = getActivity().getActivityTab();
         final CallbackHelper pageLoadFinishedHelper = new CallbackHelper();
-        tab.addObserver(new EmptyTabObserver() {
-            @Override
-            public void onPageLoadFinished(Tab tab, GURL url) {
-                pageLoadFinishedHelper.notifyCalled();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.addObserver(new EmptyTabObserver() {
+                @Override
+                public void onPageLoadFinished(Tab tab, GURL url) {
+                    pageLoadFinishedHelper.notifyCalled();
+                }
+            });
         });
         pageLoadFinishedHelper.waitForCallback(0);
         CriteriaHelper.pollInstrumentationThread(() -> {
@@ -915,16 +919,18 @@
 
         final Tab tab = getActivity().getActivityTab();
         final CallbackHelper pageLoadFinishedHelper = new CallbackHelper();
-        tab.addObserver(new EmptyTabObserver() {
-            @Override
-            public void onLoadUrl(Tab tab, LoadUrlParams params, int loadType) {
-                assertEquals(referrer, params.getReferrer().getUrl());
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.addObserver(new EmptyTabObserver() {
+                @Override
+                public void onLoadUrl(Tab tab, LoadUrlParams params, int loadType) {
+                    assertEquals(referrer, params.getReferrer().getUrl());
+                }
 
-            @Override
-            public void onPageLoadFinished(Tab tab, GURL url) {
-                pageLoadFinishedHelper.notifyCalled();
-            }
+                @Override
+                public void onPageLoadFinished(Tab tab, GURL url) {
+                    pageLoadFinishedHelper.notifyCalled();
+                }
+            });
         });
         Assert.assertTrue("CustomTabContentHandler can't handle intent with same session",
                 TestThreadUtils.runOnUiThreadBlockingNoException(
@@ -955,16 +961,18 @@
 
         final Tab tab = getActivity().getActivityTab();
         final CallbackHelper pageLoadFinishedHelper = new CallbackHelper();
-        tab.addObserver(new EmptyTabObserver() {
-            @Override
-            public void onLoadUrl(Tab tab, LoadUrlParams params, int loadType) {
-                assertEquals(referrer, params.getReferrer().getUrl());
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.addObserver(new EmptyTabObserver() {
+                @Override
+                public void onLoadUrl(Tab tab, LoadUrlParams params, int loadType) {
+                    assertEquals(referrer, params.getReferrer().getUrl());
+                }
 
-            @Override
-            public void onPageLoadFinished(Tab tab, GURL url) {
-                pageLoadFinishedHelper.notifyCalled();
-            }
+                @Override
+                public void onPageLoadFinished(Tab tab, GURL url) {
+                    pageLoadFinishedHelper.notifyCalled();
+                }
+            });
         });
         Assert.assertTrue("CustomTabContentHandler can't handle intent with same session",
                 TestThreadUtils.runOnUiThreadBlockingNoException(
@@ -1589,7 +1597,8 @@
                 tabbedActivity.set((ChromeTabbedActivity) activity);
             }
         };
-        ApplicationStatus.registerStateListenerForAllActivities(listener);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> ApplicationStatus.registerStateListenerForAllActivities(listener));
 
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
             TabTestUtils.openNewTab(cctActivity.getActivityTab(), new GURL("about:blank"), null,
@@ -1609,7 +1618,8 @@
                     ChromeTabUtils.getUrlStringOnUiThread(tab), is("about:blank"));
         });
 
-        ApplicationStatus.unregisterActivityStateListener(listener);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> ApplicationStatus.unregisterActivityStateListener(listener));
     }
 
     /** Maybe prerenders a URL with a referrer, then launch it with another one. */
@@ -1892,7 +1902,7 @@
                 tabHiddenHelper.notifyCalled();
             }
         };
-        tabToBeReparented.addObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tabToBeReparented.addObserver(observer));
         PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
             getActivity().getComponent().resolveNavigationController()
                     .openCurrentUrlInBrowser(true);
@@ -1919,11 +1929,14 @@
         assertEquals("The tab should never be hidden during the reparenting process", 0,
                 tabHiddenHelper.getCallCount());
         Assert.assertFalse(TabTestUtils.isCustomTab(tabToBeReparented));
-        tabToBeReparented.removeObserver(observer);
-        RewindableIterator<TabObserver> observers = TabTestUtils.getTabObservers(tabToBeReparented);
-        while (observers.hasNext()) {
-            Assert.assertFalse(observers.next() instanceof CustomTabObserver);
-        }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tabToBeReparented.removeObserver(observer);
+            RewindableIterator<TabObserver> observers =
+                    TabTestUtils.getTabObservers(tabToBeReparented);
+            while (observers.hasNext()) {
+                Assert.assertFalse(observers.next() instanceof CustomTabObserver);
+            }
+        });
         return newActivity;
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabDeferredStartupTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabDeferredStartupTest.java
index d389806..7bab4c5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabDeferredStartupTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabDeferredStartupTest.java
@@ -39,6 +39,7 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.url.GURL;
 
 import java.util.Arrays;
@@ -155,14 +156,16 @@
     @DisableFeatures(ChromeFeatureList.TRUSTED_WEB_ACTIVITY_QUALITY_ENFORCEMENT_FORCED)
     // TODO(eirage): Make this test work with quality enforcement.
     public void testPageIsLoadedOnDeferredStartup() throws Exception {
-        PageLoadFinishedTabObserver tabObserver = new PageLoadFinishedTabObserver();
-        NewTabObserver newTabObserver = new NewTabObserver(tabObserver);
-        TabModelSelectorBase.setObserverForTests(newTabObserver);
-        ApplicationStatus.registerStateListenerForAllActivities(newTabObserver);
         CallbackHelper helper = new CallbackHelper();
-        PageIsLoadedDeferredStartupHandler handler =
-                new PageIsLoadedDeferredStartupHandler(tabObserver, helper);
-        DeferredStartupHandler.setInstanceForTests(handler);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            PageLoadFinishedTabObserver tabObserver = new PageLoadFinishedTabObserver();
+            NewTabObserver newTabObserver = new NewTabObserver(tabObserver);
+            TabModelSelectorBase.setObserverForTests(newTabObserver);
+            ApplicationStatus.registerStateListenerForAllActivities(newTabObserver);
+            PageIsLoadedDeferredStartupHandler handler =
+                    new PageIsLoadedDeferredStartupHandler(tabObserver, helper);
+            DeferredStartupHandler.setInstanceForTests(handler);
+        });
         CustomTabActivityTypeTestUtils.launchActivity(
                 mActivityType, mActivityTestRule, "about:blank");
         helper.waitForCallback(0);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabPostMessageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabPostMessageTest.java
index f5e2e92e..f14799f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabPostMessageTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabPostMessageTest.java
@@ -189,12 +189,14 @@
                 connection.postMessage(token, "Message", null) == CustomTabsService.RESULT_SUCCESS);
 
         final CallbackHelper renderProcessCallback = new CallbackHelper();
-        new WebContentsObserver(mCustomTabActivityTestRule.getWebContents()) {
-            @Override
-            public void renderProcessGone(boolean wasOomProtected) {
-                renderProcessCallback.notifyCalled();
-            }
-        };
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            new WebContentsObserver(mCustomTabActivityTestRule.getWebContents()) {
+                @Override
+                public void renderProcessGone(boolean wasOomProtected) {
+                    renderProcessCallback.notifyCalled();
+                }
+            };
+        });
         PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
             WebContentsUtils.simulateRendererKilled(
                     mCustomTabActivityTestRule.getActivity().getActivityTab().getWebContents(),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/dom_distiller/DistillabilityServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/dom_distiller/DistillabilityServiceTest.java
index 809daa4..30f56296 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/dom_distiller/DistillabilityServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/dom_distiller/DistillabilityServiceTest.java
@@ -26,6 +26,7 @@
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.components.infobars.InfoBar;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer.OnPageFinishedHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TestWebContentsObserver;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.test.util.UiRestriction;
@@ -61,7 +62,7 @@
 
         final CallbackHelper readerShownCallbackHelper = new CallbackHelper();
 
-        mActivityTestRule.getInfoBarContainer().addObserver(new InfoBarContainerObserver() {
+        InfoBarContainerObserver infoBarObserver = new InfoBarContainerObserver() {
             @Override
             public void onAddInfoBar(InfoBarContainer container, InfoBar infoBar, boolean isFirst) {
                 if (infoBar instanceof ReaderModeInfoBar) readerShownCallbackHelper.notifyCalled();
@@ -77,10 +78,12 @@
             @Override
             public void onInfoBarContainerShownRatioChanged(
                     InfoBarContainer container, float shownRatio) {}
-        });
+        };
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getInfoBarContainer().addObserver(infoBarObserver));
 
-        TestWebContentsObserver observer =
-                new TestWebContentsObserver(mActivityTestRule.getWebContents());
+        TestWebContentsObserver observer = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> new TestWebContentsObserver(mActivityTestRule.getWebContents()));
         OnPageFinishedHelper finishHelper = observer.getOnPageFinishedHelper();
 
         // Navigate to a native page.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java
index c8e2b20..a0eb7ea 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java
@@ -367,12 +367,15 @@
 
         try {
             DownloadInfo info = new DownloadInfo.Builder().build();
-            final OMADownloadHandlerForTest omaHandler = new OMADownloadHandlerForTest(context) {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    // Ignore all the broadcasts.
-                }
-            };
+            final OMADownloadHandlerForTest omaHandler =
+                    TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+                        return new OMADownloadHandlerForTest(context) {
+                            @Override
+                            public void onReceive(Context context, Intent intent) {
+                                // Ignore all the broadcasts.
+                            }
+                        };
+                    });
 
             omaHandler.clearPendingOMADownloads();
             omaHandler.downloadOMAContent(0, info, omaInfo);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/dialogs/DownloadLaterDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/dialogs/DownloadLaterDialogTest.java
index 7591673..93b7491 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/dialogs/DownloadLaterDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/dialogs/DownloadLaterDialogTest.java
@@ -127,7 +127,7 @@
                     DownloadLaterDialogProperties.SHOW_DATE_TIME_PICKER_OPTION, showDateTimePicker);
         }
 
-        return builder.build();
+        return TestThreadUtils.runOnUiThreadBlockingNoException(() -> builder.build());
     }
 
     private void showDialog() {
@@ -200,7 +200,8 @@
     public void testInitialSelectionOnWifiWithEditLocation() {
         mModel = createModel(
                 DownloadLaterDialogChoice.ON_WIFI, DownloadLaterPromptStatus.SHOW_PREFERENCE);
-        mModel.set(DownloadLaterDialogProperties.LOCATION_TEXT, "location");
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mModel.set(DownloadLaterDialogProperties.LOCATION_TEXT, "location"));
         showDialog();
         assertPositiveButtonText(POSITIVE_BUTTON_TEXT);
         assertShowAgainCheckBox(true, View.VISIBLE, false);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationDialogTest.java
index 0d97d78..e1be92a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationDialogTest.java
@@ -95,8 +95,10 @@
                 .thenReturn(PRIMARY_STORAGE_PATH);
         Profile.setLastUsedProfileForTesting(mProfileMock);
         mAppModalPresenter = new AppModalPresenter(getActivity());
-        mModalDialogManager =
-                new ModalDialogManager(mAppModalPresenter, ModalDialogManager.ModalDialogType.APP);
+        mModalDialogManager = TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            return new ModalDialogManager(
+                    mAppModalPresenter, ModalDialogManager.ModalDialogType.APP);
+        });
         Map<String, Boolean> features = new HashMap<>();
         features.put(ChromeFeatureList.SMART_SUGGESTION_FOR_LARGE_DOWNLOADS, false);
         ChromeFeatureList.setTestFeatures(features);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
index 7750af0..d0a1b56 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
@@ -219,6 +219,16 @@
         }
     }
 
+    private void updateLocationBar() {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            LocationBarMediator mediator = mLocationBarCoordinator.getMediatorForTesting();
+            mediator.onIncognitoStateChanged();
+            mediator.onPrimaryColorChanged();
+            mediator.onSecurityStateChanged();
+            mediator.onUrlChanged();
+        });
+    }
+
     @Test
     @MediumTest
     public void testSetSearchQueryFocusesUrlBar() {
@@ -502,6 +512,35 @@
 
     @Test
     @MediumTest
+    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
+    public void testFocusLogic_lenButtonVisibilityOnLocationBarIncognitoStateChange() {
+        startActivityNormally();
+        doReturn(true).when(mVoiceRecognitionHandler).isVoiceSearchEnabled();
+        doReturn(false).when(mLensController).isLensEnabled(any());
+        String url = mActivityTestRule.getEmbeddedTestServerRule().getServer().getURLWithHostName(
+                HOSTNAME, "/");
+        // Test when incognito is true.
+        mActivityTestRule.loadUrlInNewTab(url, /** incognito = */ true);
+        updateLocationBar();
+        onView(withId(R.id.lens_camera_button_end)).check(matches(not(isDisplayed())));
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mUrlBar.requestFocus(); });
+        ViewUtils.waitForView(allOf(withId(R.id.lens_camera_button_end), not(isDisplayed())));
+        ViewUtils.waitForView(allOf(withId(R.id.mic_button), isDisplayed()));
+        assertTheLastVisibleButtonInSearchBoxById(R.id.mic_button);
+
+        // Test when incognito is false.
+        doReturn(true).when(mLensController).isLensEnabled(any());
+        mActivityTestRule.loadUrlInNewTab(url, /** incognito = */ false);
+        updateLocationBar();
+        onView(withId(R.id.lens_camera_button_end)).check(matches(not(isDisplayed())));
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mUrlBar.requestFocus(); });
+        ViewUtils.waitForView(allOf(withId(R.id.lens_camera_button_end), isDisplayed()));
+        assertTheLastVisibleButtonInSearchBoxById(R.id.lens_camera_button_end);
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mUrlBar.clearFocus(); });
+    }
+
+    @Test
+    @MediumTest
     @CommandLineFlags.
     Add({"enable-features=" + ChromeFeatureList.LENS_CAMERA_ASSISTED_SEARCH + "<FakeStudyName",
             "force-fieldtrials=FakeStudyName/Enabled",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninCheckerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninCheckerTest.java
index c55e515d..69b07bba 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninCheckerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninCheckerTest.java
@@ -57,7 +57,7 @@
 
     @Rule
     public final AccountManagerTestRule mAccountManagerTestRule =
-            new AccountManagerTestRule(new FakeAccountManagerFacade(null) {
+            new AccountManagerTestRule(new FakeAccountManagerFacade() {
                 @Override
                 public void checkChildAccountStatus(
                         Account account, ChildAccountStatusListener listener) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/AccountManagementFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/AccountManagementFragmentTest.java
index eef95dd..8537e1ddc 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/AccountManagementFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/AccountManagementFragmentTest.java
@@ -55,7 +55,7 @@
 public class AccountManagementFragmentTest {
     private static final String CHILD_ACCOUNT_EMAIL = "child@gmail.com";
 
-    private final FakeAccountManagerFacade mFakeFacade = new FakeAccountManagerFacade(null) {
+    private final FakeAccountManagerFacade mFakeFacade = new FakeAccountManagerFacade() {
         @Override
         public void checkChildAccountStatus(Account account, ChildAccountStatusListener listener) {
             listener.onStatusReady(CHILD_ACCOUNT_EMAIL.equals(account.name)
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataTest.java
index 55111b8..2b35cf4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataTest.java
@@ -18,6 +18,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import org.chromium.base.UserDataHost;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.UiThreadTest;
@@ -734,4 +735,57 @@
         });
         callbackHelper.waitForCallback(0);
     }
+
+    @UiThreadTest
+    @SmallTest
+    @Test
+    public void testUninitializedTabDisabled() throws TimeoutException {
+        TabImpl tab = mock(TabImpl.class);
+        doReturn(false).when(tab).isInitialized();
+        CallbackHelper callbackHelper = new CallbackHelper();
+        ShoppingPersistedTabData.from(tab, (res) -> {
+            Assert.assertNull(res);
+            callbackHelper.notifyCalled();
+        });
+        callbackHelper.waitForCallback(0);
+    }
+
+    @SmallTest
+    @Test
+    @CommandLineFlags.
+    Add({"force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/true"})
+    public void testTabDestroyedSupplier() {
+        ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
+                mOptimizationGuideBridgeJniMock,
+                HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
+                ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
+                        .BUYABLE_PRODUCT_INITIAL);
+        ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
+                mOptimizationGuideBridgeJniMock,
+                HintsProto.OptimizationType.SHOPPING_PAGE_PREDICTOR.getNumber(),
+                OptimizationGuideDecision.TRUE, null);
+        TabImpl tab = mock(TabImpl.class);
+        doReturn(ShoppingPersistedTabDataTestUtils.TAB_ID).when(tab).getId();
+        doReturn(ShoppingPersistedTabDataTestUtils.IS_INCOGNITO).when(tab).isIncognito();
+        CriticalPersistedTabData criticalPersistedTabData = new CriticalPersistedTabData(tab);
+        criticalPersistedTabData.setTimestampMillis(
+                System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1));
+        for (boolean isInitialized : new boolean[] {false, true}) {
+            doReturn(isInitialized).when(tab).isInitialized();
+            Semaphore semaphore = new Semaphore(0);
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                UserDataHost userDataHost = new UserDataHost();
+                userDataHost.setUserData(CriticalPersistedTabData.class, criticalPersistedTabData);
+                doReturn(userDataHost).when(tab).getUserDataHost();
+                ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> {
+                    if (isInitialized) {
+                        Assert.assertNotNull(shoppingPersistedTabData);
+                    } else {
+                        Assert.assertNull(shoppingPersistedTabData);
+                    }
+                    semaphore.release();
+                });
+            });
+        }
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabPersistentStoreTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabPersistentStoreTest.java
index 328220e..74b4a7a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabPersistentStoreTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabPersistentStoreTest.java
@@ -355,7 +355,10 @@
                 mAppContext, "TabPersistentStoreTest", Integer.toString(SELECTOR_INDEX));
         TabStateDirectory.setBaseStateDirectoryForTests(mMockDirectory.getBaseDirectory());
 
-        ApplicationStatus.registerStateListenerForActivity(mActivityStateListener, mChromeActivity);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            ApplicationStatus.registerStateListenerForActivity(
+                    mActivityStateListener, mChromeActivity);
+        });
     }
 
     @After
@@ -363,8 +366,8 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             sTabWindowManager.onActivityStateChange(mChromeActivity, ActivityState.DESTROYED);
             ApplicationStatus.onStateChangeForTesting(mChromeActivity, ActivityState.DESTROYED);
+            ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
         });
-        ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
         mMockDirectory.tearDown();
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/childaccounts/ChildAccountServiceTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/childaccounts/ChildAccountServiceTest.java
index 74851b97..30ddd26 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/childaccounts/ChildAccountServiceTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/childaccounts/ChildAccountServiceTest.java
@@ -55,7 +55,7 @@
             AccountUtils.createAccountFromName("adult.account2@gmail.com");
     private static final long FAKE_NATIVE_CALLBACK = 1000L;
 
-    private final FakeAccountManagerFacade mFakeFacade = spy(new FakeAccountManagerFacade(null) {
+    private final FakeAccountManagerFacade mFakeFacade = spy(new FakeAccountManagerFacade() {
         @Override
         public void checkChildAccountStatus(Account account, ChildAccountStatusListener listener) {
             listener.onStatusReady(account.name.startsWith("child")
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/sync/AndroidSyncSettingsTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/sync/AndroidSyncSettingsTest.java
index c8a6e69..c97160e 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/sync/AndroidSyncSettingsTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/sync/AndroidSyncSettingsTest.java
@@ -138,7 +138,7 @@
             SyncService.overrideForTests(mSyncService);
         });
 
-        FakeAccountManagerFacade fakeAccountManagerFacade = new FakeAccountManagerFacade(null);
+        FakeAccountManagerFacade fakeAccountManagerFacade = new FakeAccountManagerFacade();
         AccountManagerFacadeProvider.setInstanceForTests(fakeAccountManagerFacade);
         mAccount = AccountUtils.createAccountFromName("account@example.com");
         fakeAccountManagerFacade.addAccount(mAccount);
diff --git a/chrome/android/monochrome/scripts/monochrome_apk_checker_test.py b/chrome/android/monochrome/scripts/monochrome_apk_checker_test.py
index 7439e24..9806d37 100644
--- a/chrome/android/monochrome/scripts/monochrome_apk_checker_test.py
+++ b/chrome/android/monochrome/scripts/monochrome_apk_checker_test.py
@@ -86,9 +86,9 @@
 def DumpAPK(apk):
   args = ['unzip', '-lv']
   args.append(apk)
-  content = subprocess.check_output(args)
+  content = subprocess.check_output(args, universal_newlines=True)
   apk_entries = []
-  with contextlib.closing(io.BytesIO(content)) as f:
+  with contextlib.closing(io.StringIO(content)) as f:
     for line in f:
       match = ZIP_ENTRY.match(line)
       if match:
diff --git a/chrome/android/monochrome/scripts/monochrome_python_tests.py b/chrome/android/monochrome/scripts/monochrome_python_tests.py
index 08edc1c..ec73402f 100755
--- a/chrome/android/monochrome/scripts/monochrome_python_tests.py
+++ b/chrome/android/monochrome/scripts/monochrome_python_tests.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2.7
+#!/usr/bin/env vpython3
 #
 # Copyright 2020 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
diff --git a/chrome/app/BUILD.gn b/chrome/app/BUILD.gn
index a23cc79..baca45b0 100644
--- a/chrome/app/BUILD.gn
+++ b/chrome/app/BUILD.gn
@@ -54,6 +54,16 @@
   }
 }
 
+if (is_mac) {
+  source_set("notification_metrics") {
+    sources = [
+      "notification_metrics.cc",
+      "notification_metrics.h",
+    ]
+    deps = [ "//base" ]
+  }
+}
+
 if (is_android) {
   android_generated_java_resources =
       [ "java/res/values/generated_resources.xml" ] +
diff --git a/chrome/app/chrome_main.cc b/chrome/app/chrome_main.cc
index 9b1ab85..ac634ab 100644
--- a/chrome/app/chrome_main.cc
+++ b/chrome/app/chrome_main.cc
@@ -20,6 +20,7 @@
 
 #if defined(OS_MAC)
 #include "chrome/app/chrome_main_mac.h"
+#include "chrome/app/notification_metrics.h"
 #endif
 
 #if defined(OS_WIN) || defined(OS_LINUX)
@@ -148,6 +149,15 @@
       ::switches::kEnableCrashpad);
 #endif
 
+#if defined(OS_MAC)
+  // Gracefully exit if the system tried to launch the macOS notification helper
+  // app when a user clicked on a notification.
+  if (IsAlertsHelperLaunchedViaNotificationAction()) {
+    LogLaunchedViaNotificationAction(NotificationActionSource::kHelperApp);
+    return 0;
+  }
+#endif
+
   int rv = content::ContentMain(params);
 
   return rv;
diff --git a/chrome/app/chrome_main_mac.h b/chrome/app/chrome_main_mac.h
index 0de6cc8f..984a755 100644
--- a/chrome/app/chrome_main_mac.h
+++ b/chrome/app/chrome_main_mac.h
@@ -20,4 +20,10 @@
 // process.
 void SetUpBundleOverrides();
 
+// Checks if the system launched the alerts helper app via a notification
+// action. If that's the case we want to gracefully exit the process as we can't
+// handle the click this way. Instead we rely on the browser process to re-spawn
+// the helper if it got killed unexpectedly.
+bool IsAlertsHelperLaunchedViaNotificationAction();
+
 #endif  // CHROME_APP_CHROME_MAIN_MAC_H_
diff --git a/chrome/app/chrome_main_mac.mm b/chrome/app/chrome_main_mac.mm
index 40aa837..82c8652 100644
--- a/chrome/app/chrome_main_mac.mm
+++ b/chrome/app/chrome_main_mac.mm
@@ -8,14 +8,17 @@
 
 #include <string>
 
+#include "base/command_line.h"
 #include "base/files/file_path.h"
 #import "base/mac/bundle_locations.h"
 #import "base/mac/foundation_util.h"
 #include "base/path_service.h"
+#include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_paths_internal.h"
 #include "content/public/common/content_paths.h"
+#include "content/public/common/content_switches.h"
 
 void SetUpBundleOverrides() {
   @autoreleasepool {
@@ -33,3 +36,23 @@
     base::PathService::Override(content::CHILD_PROCESS_EXE, child_exe_path);
   }
 }
+
+bool IsAlertsHelperLaunchedViaNotificationAction() {
+  // We allow the main Chrome app to be launched via a notification action. We
+  // detect and log that to UMA by checking the passed in NSNotification in
+  // -applicationDidFinishLaunching: (//chrome/browser/app_controller_mac.mm).
+  if (!base::mac::IsBackgroundOnlyProcess())
+    return false;
+
+  // If we have a process type then we were not launched by the system.
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessType))
+    return false;
+
+  base::FilePath path;
+  if (!base::PathService::Get(base::FILE_EXE, &path))
+    return false;
+
+  // Check if our executable name matches the helper app for notifications.
+  std::string helper_name = path.BaseName().value();
+  return base::EndsWith(helper_name, chrome::kMacHelperSuffixAlerts);
+}
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index e0f4950..cfdaff2 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -3554,8 +3554,8 @@
       <message name="IDS_TAB_SHARING_INFOBAR_STOP_BUTTON" desc="Text displayed on the button to stop sharing a tab.">
        Stop sharing
       </message>
-      <message name="IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON" desc="The user is sharing (presenting) a tab via a screensharing app like Google Meet. This button allows the user to continue sharing a tab but view a different tab [url] at the same time.">
-       View tab <ph name="TAB_ORIGIN">$1<ex>meet.google.com</ex></ph>
+      <message name="IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON" desc="The user is sharing (presenting) a tab via a screensharing app like Google Meet. This button allows the user to continue sharing a tab but view a different tab [url] at the same time. The URL should not appear in the middle of the button in any language; please observe the structure of [message: url].">
+       View tab: <ph name="TAB_ORIGIN">$1<ex>meet.google.com</ex></ph>
       </message>
       <message name="IDS_TAB_SHARING_INFOBAR_SWITCH_TO_CAPTURER_BUTTON" desc="Text displayed on an infobar button offering to change focus to the capturer if its hostname cannot be determined. (Otherwise, IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON will be used.)">
        Switch to capturing tab
@@ -6354,6 +6354,9 @@
       <message name="IDS_TAB_GROUPS_NEW_GROUP_PROMO" desc="Text shown on promotional UI encouraging users to create a tab group in their tab strip">
         To group tabs together, right-click a tab
       </message>
+      <message name="IDS_TAB_GROUPS_UNNAMED_GROUP_TOOLTIP" desc="Static tooltip to display on hover over unnamed tab group.">
+        Unnamed Tab Group
+      </message>
       <if expr="use_titlecase">
         <message name="IDS_PROMO_DISMISS_BUTTON" desc="Text shown on the button for the user to acknowledge that they understand the message">
           Got It
@@ -8659,14 +8662,6 @@
         Failed To Create Data Directory
       </message>
 
-      <!-- Multiple download warning-->
-      <message name="IDS_MULTI_DOWNLOAD_WARNING" desc="Warning invoked if multiple downloads are attempted without user interaction.">
-        <ph name="ORIGIN">$1<ex>https://backgroundfetch.com</ex></ph> wants to download multiple files
-      </message>
-      <message name="IDS_MULTI_DOWNLOAD_PERMISSION_FRAGMENT" desc="Permission request shown if multiple downloads are attempted without user interaction and there are multiple permissions requested. Follows a prompt: 'This site would like to:'">
-        Download multiple files
-      </message>
-
       <if expr="use_titlecase">
         <message name="IDS_RECENT_TABS_MENU" desc="In Title Case: The title of the Recent Tabs menu in the wrench menu.">
           Recent Tabs
@@ -10802,9 +10797,6 @@
     </message>
 
     <!-- Security Key permission -->
-    <message name="IDS_SECURITY_KEY_ATTESTATION_PERMISSION_FRAGMENT" desc="A permission prompt shown to a user when a website wants to see information that identifies the user's Security Key, such as make and model number. The text '[website URL] wants to:' precedes this string. The 'make' of a device is the brand name of the manufacturer, e.g. Yubikey is a make of Security Key. The 'model' of a device is the specific product, e.g. Yubikey Neo is a model of Security Key.">
-      See the make and model of your Security Key
-    </message>
     <if expr="is_android">
       <message name="IDS_SECURITY_KEY_ATTESTATION_INFOBAR_QUESTION" desc="An infobar prompt shown to a user when a website wants to see information that identifies the user's Security Key, such as make and model number. The 'make' of a device is the brand name of the manufacturer, e.g. Yubikey is a make of Security Key. The 'model' of a device is the specific product, e.g. Yubikey Neo is a model of Security Key.">
         <ph name="URL">
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_GROUPS_UNNAMED_GROUP_TOOLTIP.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_GROUPS_UNNAMED_GROUP_TOOLTIP.png.sha1
new file mode 100644
index 0000000..3d4d80c
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TAB_GROUPS_UNNAMED_GROUP_TOOLTIP.png.sha1
@@ -0,0 +1 @@
+a5b7349960b8b9e613efe95219f10c0429d12335
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON.png.sha1
index a0a21e9..4f3f53a 100644
--- a/chrome/app/generated_resources_grd/IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON.png.sha1
@@ -1 +1 @@
-7d6b2bf6181efb0ca1a4ddfe749b404167bc6db1
\ No newline at end of file
+a57386d693981289c82c8ac2d8ca704e72d0e715
\ No newline at end of file
diff --git a/chrome/app/notification_metrics.cc b/chrome/app/notification_metrics.cc
new file mode 100644
index 0000000..30338a3
--- /dev/null
+++ b/chrome/app/notification_metrics.cc
@@ -0,0 +1,12 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/app/notification_metrics.h"
+
+#include "base/metrics/histogram_functions.h"
+
+void LogLaunchedViaNotificationAction(NotificationActionSource source) {
+  base::UmaHistogramEnumeration(
+      "Notifications.macOS.LaunchedViaNotificationAction", source);
+}
diff --git a/chrome/app/notification_metrics.h b/chrome/app/notification_metrics.h
new file mode 100644
index 0000000..174c0d42
--- /dev/null
+++ b/chrome/app/notification_metrics.h
@@ -0,0 +1,22 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_APP_NOTIFICATION_METRICS_H_
+#define CHROME_APP_NOTIFICATION_METRICS_H_
+
+// Describes from which app the notification action came for. This enum is used
+// in UMA. Do not delete or re-order entries. New entries should only be added
+// at the end.
+enum class NotificationActionSource {
+  // Action for the browser app, usually from a banner style notification.
+  kBrowser = 0,
+  // Action for the helper app, usually from an alert style notification.
+  kHelperApp = 1,
+  kMaxValue = kHelperApp,
+};
+
+// Logs to UMA that we got launched via the OS to handle a notification action.
+void LogLaunchedViaNotificationAction(NotificationActionSource source);
+
+#endif  // CHROME_APP_NOTIFICATION_METRICS_H_
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 246b273..da4b7d8 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -2860,9 +2860,15 @@
   <message name="IDS_SETTINGS_USERS_REMOVE_USER_TOOLTIP" desc="Tooltip for the button that removes a user from the list of restricted sign in users.">
     Remove <ph name="USER_NAME">$1<ex>John Smith</ex></ph>
   </message>
+  <message name="IDS_SETTINGS_USERS_USER_REMOVED_MESSAGE" desc="Message announced by screen reader after removal of the user from the list of restricted sign in users.">
+    <ph name="USER_NAME_OR_EMAIL">$1<ex>John Smith</ex></ph> removed
+  </message>
   <message name="IDS_SETTINGS_USERS_ADD_USERS" desc="Label the dialog for adding users.">
     Add user
   </message>
+  <message name="IDS_SETTINGS_USERS_USER_ADDED_MESSAGE" desc="Message announced by screen reader after addition of the user to the list of restricted sign in users.">
+    <ph name="USER_EMAIL">$1<ex>user@gmail.com</ex></ph> added
+  </message>
   <message name="IDS_SETTINGS_USERS_ADD_USERS_EMAIL" desc="Label for the email input field for adding users.">
     Email address
   </message>
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_USERS_USER_ADDED_MESSAGE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_USERS_USER_ADDED_MESSAGE.png.sha1
new file mode 100644
index 0000000..c165899
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_USERS_USER_ADDED_MESSAGE.png.sha1
@@ -0,0 +1 @@
+5425239677cd178ff7929de1cdcadb6fc18fedc2
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_USERS_USER_REMOVED_MESSAGE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_USERS_USER_REMOVED_MESSAGE.png.sha1
new file mode 100644
index 0000000..0249aa9f
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_USERS_USER_REMOVED_MESSAGE.png.sha1
@@ -0,0 +1 @@
+4359ba281be458efd71e78d6fff7fd19a62f4a12
\ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index d078d26..b44922c 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -1047,6 +1047,35 @@
     Open certain file types automatically after downloading
   </message>
 
+  <!-- Downloads Page: Web Drive Connection -->
+  <message name="IDS_SETTINGS_DOWNLOAD_CONNECTION_TITLE" desc="Label for the checkbox which enables a prompt for the user to choose a download location for each download instead of using the default.">
+    <ph name="WEB_DRIVE">$1<ex>Google Drive</ex></ph> Connection
+  </message>
+  <message name="IDS_SETTINGS_DOWNLOAD_CONNECTION_LEARN_MORE" desc="Text to be displayed to take user to relevant CBCM article.">
+    Learn more
+  </message>
+  <message name="IDS_SETTINGS_DOWNLOAD_CONNECTION_UNLINKED_MESSAGE" desc="Meesage to be displayed when downloads are set to be routed to web drive by CBCM policy but the user has not linked a web drive account yet.">
+    For data safety and security, your organization requries all eligible downloads to be saved to your organization's <ph name="WEB_DRIVE">$1<ex>Google Drive</ex></ph> account.
+  </message>
+<message name="IDS_SETTINGS_DOWNLOAD_CONNECTION_SIGN_IN_BUTTON" desc="Label for the checkbox which enables a prompt for the user to choose a download location for each download instead of using the default.">
+    Sign in to <ph name="WEB_DRIVE">$1<ex>Google Drive</ex></ph>
+  </message>
+  <message name="IDS_SETTINGS_DOWNLOAD_CONNECTION_LINKED_MESSAGE" desc="Meesage to be displayed when downloads are set to be routed to web drive by CBCM policy and the user has linked a web drive account already.">
+    All eligible downloads are routed to your organization's <ph name="WEB_DRIVE">$1<ex>Google Drive</ex></ph> account.
+  </message>
+  <message name="IDS_SETTINGS_DOWNLOAD_CONNECTION_SIGN_OUT_BUTTON" desc="Label for the checkbox which enables a prompt for the user to choose a download location for each download instead of using the default.">
+    Unlink Account
+  </message>
+  <message name="IDS_SETTINGS_DOWNLOAD_CONNECTION_ACCOUNT_TITLE" desc="Label for the checkbox which enables a prompt for the user to choose a download location for each download instead of using the default.">
+    Account
+  </message>
+  <message name="IDS_SETTINGS_DOWNLOAD_WEB_DRIVE_LOCATION" desc="Label for the input which allows the user to specify the default download directory, if the file system connector is enabled." meaning="File location">
+    <ph name="WEB_DRIVE">$1<ex>Google Drive</ex></ph> Location
+  </message>
+  <message name="IDS_SETTINGS_DOWNLOAD_LOCAL_LOCATION" desc="Label for the input which allows the user to specify the default download directory, if the file system connector is enabled." meaning="File location">
+    Local Location
+  </message>
+
   <!-- On Startup Page -->
   <message name="IDS_SETTINGS_ON_STARTUP" desc="Name of the on startup page.">
     On startup
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_ACCOUNT_TITLE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_ACCOUNT_TITLE.png.sha1
new file mode 100644
index 0000000..72b781e
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_ACCOUNT_TITLE.png.sha1
@@ -0,0 +1 @@
+d938757dfee974fd414cc9792a492deefdb2b34c
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_LEARN_MORE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_LEARN_MORE.png.sha1
new file mode 100644
index 0000000..af1f67e2
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_LEARN_MORE.png.sha1
@@ -0,0 +1 @@
+0346debb2bbf26e437940586bf61cc8ee0262cf6
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_LINKED_MESSAGE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_LINKED_MESSAGE.png.sha1
new file mode 100644
index 0000000..72b781e
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_LINKED_MESSAGE.png.sha1
@@ -0,0 +1 @@
+d938757dfee974fd414cc9792a492deefdb2b34c
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_SIGN_IN_BUTTON.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_SIGN_IN_BUTTON.png.sha1
new file mode 100644
index 0000000..bfd2eb6
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_SIGN_IN_BUTTON.png.sha1
@@ -0,0 +1 @@
+93ed75d17e311d8b7ef3fe3c87b24af2892bee3b
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_SIGN_OUT_BUTTON.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_SIGN_OUT_BUTTON.png.sha1
new file mode 100644
index 0000000..72b781e
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_SIGN_OUT_BUTTON.png.sha1
@@ -0,0 +1 @@
+d938757dfee974fd414cc9792a492deefdb2b34c
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_TITLE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_TITLE.png.sha1
new file mode 100644
index 0000000..72b781e
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_TITLE.png.sha1
@@ -0,0 +1 @@
+d938757dfee974fd414cc9792a492deefdb2b34c
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_UNLINKED_MESSAGE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_UNLINKED_MESSAGE.png.sha1
new file mode 100644
index 0000000..bfd2eb6
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_CONNECTION_UNLINKED_MESSAGE.png.sha1
@@ -0,0 +1 @@
+93ed75d17e311d8b7ef3fe3c87b24af2892bee3b
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_LOCAL_LOCATION.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_LOCAL_LOCATION.png.sha1
new file mode 100644
index 0000000..72b781e
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_LOCAL_LOCATION.png.sha1
@@ -0,0 +1 @@
+d938757dfee974fd414cc9792a492deefdb2b34c
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_WEB_DRIVE_LOCATION.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_WEB_DRIVE_LOCATION.png.sha1
new file mode 100644
index 0000000..72b781e
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_DOWNLOAD_WEB_DRIVE_LOCATION.png.sha1
@@ -0,0 +1 @@
+d938757dfee974fd414cc9792a492deefdb2b34c
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 40a5667..d00939a1 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1922,6 +1922,7 @@
     "//chrome/browser/metrics/variations:chrome_ui_string_overrider_factory",
     "//chrome/browser/net:probe_message_proto",
     "//chrome/browser/new_tab_page/modules/drive:mojo_bindings",
+    "//chrome/browser/new_tab_page/modules/photos:mojo_bindings",
     "//chrome/browser/new_tab_page/modules/task_module:mojo_bindings",
     "//chrome/browser/notifications",
     "//chrome/browser/notifications/scheduler:factory",
@@ -2435,6 +2436,7 @@
     if (!is_official_build) {
       deps += [
         "//chromeos/components/demo_mode_app_ui",
+        "//chromeos/components/demo_mode_app_ui/mojom",
         "//chromeos/components/telemetry_extension_ui",
         "//chromeos/components/telemetry_extension_ui/mojom",
       ]
@@ -3458,6 +3460,10 @@
       "apps/app_service/publishers/extension_apps.h",
       "apps/app_service/publishers/extension_apps_base.cc",
       "apps/app_service/publishers/extension_apps_base.h",
+      "apps/apps_fetcher_service/apps_fetcher_service.cc",
+      "apps/apps_fetcher_service/apps_fetcher_service.h",
+      "apps/apps_fetcher_service/apps_fetcher_service_factory.cc",
+      "apps/apps_fetcher_service/apps_fetcher_service_factory.h",
       "apps/intent_helper/apps_navigation_throttle.cc",
       "apps/intent_helper/apps_navigation_throttle.h",
       "apps/intent_helper/apps_navigation_types.cc",
@@ -3827,6 +3833,12 @@
       "new_tab_page/modules/drive/drive_service.h",
       "new_tab_page/modules/drive/drive_service_factory.cc",
       "new_tab_page/modules/drive/drive_service_factory.h",
+      "new_tab_page/modules/photos/photos_handler.cc",
+      "new_tab_page/modules/photos/photos_handler.h",
+      "new_tab_page/modules/photos/photos_service.cc",
+      "new_tab_page/modules/photos/photos_service.h",
+      "new_tab_page/modules/photos/photos_service_factory.cc",
+      "new_tab_page/modules/photos/photos_service_factory.h",
       "new_tab_page/modules/task_module/task_module_handler.cc",
       "new_tab_page/modules/task_module/task_module_handler.h",
       "new_tab_page/modules/task_module/task_module_service.cc",
@@ -4252,7 +4264,7 @@
       "//chrome/browser/share/core:share_targets",
       "//chrome/browser/share/proto:proto",
       "//chrome/browser/ui/color:color_headers",
-      "//chrome/browser/web_applications/components",
+      "//chrome/browser/web_applications",
       "//chrome/common/apps/platform_apps",
       "//chrome/common/cart:mojo_bindings",
       "//chrome/common/importer:interfaces",
@@ -5198,8 +5210,6 @@
       "notifications/alert_dispatcher_mojo.mm",
       "notifications/mac_notification_provider_factory.h",
       "notifications/mac_notification_provider_factory.mm",
-      "notifications/notification_alert_service_bridge.h",
-      "notifications/notification_alert_service_bridge.mm",
       "notifications/notification_platform_bridge_mac.h",
       "notifications/notification_platform_bridge_mac.mm",
       "notifications/notification_platform_bridge_mac_metrics.cc",
@@ -5229,6 +5239,7 @@
       "webshare/mac/sharing_service_operation.mm",
     ]
     deps += [
+      "//chrome/app:notification_metrics",
       "//chrome/app_shim",
       "//chrome/browser/apps/app_shim",
       "//chrome/browser/ui/cocoa/notifications:common",
@@ -6038,7 +6049,6 @@
       # TODO(crbug.com/1200215): Remove cycles and simplify all dependencies.
       "//chrome/browser/web_applications",
       "//chrome/browser/web_applications/app_service",
-      "//chrome/browser/web_applications/components",
       "//chrome/browser/web_applications/extensions",
     ]
     deps += [
@@ -6046,7 +6056,6 @@
       "//chrome/browser/sync_file_system/drive_backend:sync_file_system_drive_proto",
       "//chrome/browser/web_applications",
       "//chrome/browser/web_applications/app_service",
-      "//chrome/browser/web_applications/components",
       "//chrome/browser/web_applications/extensions",
       "//chrome/common/extensions/api",
       "//chrome/common/extensions/api:extensions_features",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 67d4165..080b551b 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -96,6 +96,7 @@
 #include "components/history_clusters/core/memories_features.h"
 #include "components/invalidation/impl/invalidation_switches.h"
 #include "components/language/core/common/language_experiments.h"
+#include "components/lens/lens_features.h"
 #include "components/lookalikes/core/features.h"
 #include "components/messages/android/messages_feature.h"
 #include "components/nacl/common/buildflags.h"
@@ -3117,6 +3118,9 @@
     {"enable-dns-proxy", flag_descriptions::kEnableDnsProxyName,
      flag_descriptions::kEnableDnsProxyDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kEnableDnsProxy)},
+    {"esim-policy", flag_descriptions::kESimPolicyName,
+     flag_descriptions::kESimPolicyDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(chromeos::features::kESimPolicy)},
     {"dns-proxy-enable-doh", flag_descriptions::kDnsProxyEnableDOHName,
      flag_descriptions::kDnsProxyEnableDOHDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(::features::kDnsProxyEnableDOH)},
@@ -3465,6 +3469,9 @@
     {"sharing-hub-link-toggle", flag_descriptions::kSharingHubLinkToggleName,
      flag_descriptions::kSharingHubLinkToggleDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kSharingHubLinkToggle)},
+    {"webnotes-publish", flag_descriptions::kWebNotesPublishName,
+     flag_descriptions::kWebNotesPublishDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(content_creation::kWebNotesPublish)},
 #endif  // OS_ANDROID
     {"in-product-help-demo-mode-choice",
      flag_descriptions::kInProductHelpDemoModeChoiceName,
@@ -5051,6 +5058,13 @@
      flag_descriptions::kQuickActionSearchWidgetAndroidDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kQuickActionSearchWidgetAndroid)},
 
+    {"enable-quick-action-search-widget-android-dino-variant",
+     flag_descriptions::kQuickActionSearchWidgetAndroidDinoVariantName,
+     flag_descriptions::kQuickActionSearchWidgetAndroidDinoVariantDescription,
+     kOsAndroid,
+     FEATURE_VALUE_TYPE(
+         chrome::android::kQuickActionSearchWidgetAndroidDinoVariant)},
+
 #endif  // OS_ANDROID
 
     {"enable-layout-ng", flag_descriptions::kEnableLayoutNGName,
@@ -6323,11 +6337,6 @@
      FEATURE_VALUE_TYPE(switches::kAccountIdMigration)},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-    // TODO(https://crbug.com/1032161): Implement and enable for ChromeOS.
-    {"raw-clipboard", flag_descriptions::kRawClipboardName,
-     flag_descriptions::kRawClipboardDescription, kOsMac | kOsWin | kOsLinux,
-     FEATURE_VALUE_TYPE(blink::features::kRawClipboard)},
-
 #if BUILDFLAG(ENABLE_PAINT_PREVIEW) && defined(OS_ANDROID)
     {"paint-preview-demo", flag_descriptions::kPaintPreviewDemoName,
      flag_descriptions::kPaintPreviewDemoDescription, kOsAndroid,
@@ -6813,6 +6822,11 @@
      flag_descriptions::kMessagesForAndroidChromeSurveyName,
      flag_descriptions::kMessagesForAndroidChromeSurveyDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(messages::kMessagesForAndroidChromeSurvey)},
+    {"messages-for-android-grouped-permission",
+     flag_descriptions::kMessagesForAndroidGroupedPermissionName,
+     flag_descriptions::kMessagesForAndroidGroupedPermissionDescription,
+     kOsAndroid,
+     FEATURE_VALUE_TYPE(messages::kMessagesForAndroidGroupedPermission)},
     {"messages-for-android-infrastructure",
      flag_descriptions::kMessagesForAndroidInfrastructureName,
      flag_descriptions::kMessagesForAndroidInfrastructureDescription,
@@ -6822,6 +6836,11 @@
      flag_descriptions::kMessagesForAndroidPasswordsName,
      flag_descriptions::kMessagesForAndroidPasswordsDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(messages::kMessagesForAndroidPasswords)},
+    {"messages-for-android-permission-update",
+     flag_descriptions::kMessagesForAndroidPermissionUpdateName,
+     flag_descriptions::kMessagesForAndroidPermissionUpdateDescription,
+     kOsAndroid,
+     FEATURE_VALUE_TYPE(messages::kMessagesForAndroidPermissionUpdate)},
     {"messages-for-android-popup-blocked",
      flag_descriptions::kMessagesForAndroidPopupBlockedName,
      flag_descriptions::kMessagesForAndroidPopupBlockedDescription, kOsAndroid,
@@ -7352,6 +7371,11 @@
      FEATURE_VALUE_TYPE(
          autofill::features::kAutofillSuggestVirtualCardsOnIncompleteForm)},
 
+    {"enable-lens-region-search",
+     flag_descriptions::kEnableLensRegionSearchName,
+     flag_descriptions::kEnableLensRegionSearchDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(lens::features::kLensRegionSearch)},
+
     {"enable-penetrating-image-selection",
      flag_descriptions::kEnablePenetratingImageSelectionName,
      flag_descriptions::kEnablePenetratingImageSelectionDescription, kOsAll,
@@ -7564,6 +7588,12 @@
      FEATURE_VALUE_TYPE(omnibox::kUpdatedConnectionSecurityIndicators)},
 #endif
 
+#if defined(OS_ANDROID)
+    {"share-usage-ranking", flag_descriptions::kShareUsageRankingName,
+     flag_descriptions::kShareUsageRankingDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(features::kShareUsageRanking)},
+#endif
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git <