diff --git a/DEPS b/DEPS
index 7af9405..3a519fc 100644
--- a/DEPS
+++ b/DEPS
@@ -130,7 +130,7 @@
   # 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': '0e223be0fe07b0ae6eceb58b48cb1719c39db14e',
+  'v8_revision': '872455b5fe0cf1f09eb5550fb1e0bdd259523e8b',
   # 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.
@@ -138,15 +138,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '0029dfe28cf4ed9e6fa0928a632063f4b72b63f1',
+  'angle_revision': '6b695c3ffb27ad1df71f4f1682dc7a1d8adc9f31',
   # 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': 'd9ce258c174bc7295b92046519ce885613c14608',
+  'swiftshader_revision': '64c44b0c53c45809c70e7b075ed3a74a9f3eec0d',
   # 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': '011147f5b36f401dc40036851613e5eaba7bab03',
+  'pdfium_revision': '7abb469ef7ebbce4645165d1d0e1dbb6c8e68461',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -189,7 +189,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': '183d99e38bfe9d296913b357c80599577056700f',
+  'catapult_revision': '51e7b1437ef6375446c1bbb7db835ad5a77be25f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -237,7 +237,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.
-  'spv_tools_revision': '12b3d7e9d661b2f50815e09c34eabe310088dfe4',
+  'spv_tools_revision': '40a7940e05728bb7abd32cf88015707a1fbe7dd2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -253,7 +253,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': '3b71e65658cdb3dd5c8455241e3a5b8576b5b1dc',
+  'dawn_revision': '62e83971caa1099bdbc334817efa035047c90ddf',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -732,7 +732,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'dbec33e55f7acef520a56e1d2b4aa832311a9b13',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'd416cb794c17c0a35c872ef4bfc03cf653af9cde',
       'condition': 'checkout_linux',
   },
 
@@ -757,7 +757,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '98f4a99359d51a696dd8c1eafb78a7b4157001bb',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'e893454f797fd9d52438c6a64fd6123073825b64',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1296,7 +1296,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@4ddd89fe2fbd342f80a693a1daf17250083583fa',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@c15741b70f84ee33de6a3f597dd13462bda11056',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/docs/net-debugging.md b/android_webview/docs/net-debugging.md
new file mode 100644
index 0000000..6068b46
--- /dev/null
+++ b/android_webview/docs/net-debugging.md
@@ -0,0 +1,39 @@
+# Net debugging in WebView
+
+## Net log
+
+WebView supports the `kLogNetLog` flag to log debugging network info to a JSON
+file on disk.
+
+*** aside
+For more info on commandline flags, see
+[commandline-flags.md](./commandline-flags.md).
+***
+
+*** note
+**Note:** this requires either a `userdebug` or `eng` Android build (you can
+check with `adb shell getprop ro.build.type`). Flags cannot be enabled on
+production builds of Android.
+***
+
+1. Pick a name for the JSON file (any name will work, e.g., `jsonFile=foo.json`)
+1. Kill the app, if running
+1. Set the netlog flag:
+   ```sh
+   build/android/adb_system_webview_command_line --log-net-log=${jsonFile}
+   ```
+1. Restart the app. Reproduce whatever is of interest, and then kill the app
+   when finished
+1. Get the netlog off the device:
+   ```sh
+   # appPackageName is the package name of whatever app you're interested (ex.
+   # WebView shell is "org.chromium.webview_shell").
+   appDataDir="$(adb shell pm dump ${appPackageName} | grep 'dataDir=' | sed 's/^ *dataDir=//')" && \
+   adb pull "${appDataDir}/app_webview/${jsonFile}"
+   ```
+1. Import the JSON file into [the NetLog
+   viewer](https://chromium.googlesource.com/catapult/+/master/netlog_viewer/)
+
+For more details, see the implementation in
+[AwUrlRequestContextGetter](/android_webview/browser/net/aw_url_request_context_getter.cc).
+For support in the network service code path, see http://crbug.com/902039.
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index ac01198..522fef8 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1071,6 +1071,7 @@
     "wm/overview/overview_grid.h",
     "wm/overview/overview_item.cc",
     "wm/overview/overview_item.h",
+    "wm/overview/overview_observer.h",
     "wm/overview/overview_session.cc",
     "wm/overview/overview_session.h",
     "wm/overview/overview_utils.cc",
@@ -1274,7 +1275,6 @@
     "//ash/assistant/util",
     "//ash/components/cursor",
     "//ash/components/fast_ink",
-    "//ash/components/quick_launch/public/mojom",
     "//ash/components/shortcut_viewer/public/mojom",
     "//ash/components/tap_visualizer/public/mojom",
     "//ash/keyboard/arc",
@@ -1460,9 +1460,6 @@
   deps = [
     ":ash_shell_lib",
     ":test_support",
-    "//ash/components/quick_launch:lib",
-    "//ash/components/quick_launch/public/cpp:manifest",
-    "//ash/components/quick_launch/public/mojom",
     "//ash/components/shortcut_viewer:lib",
     "//ash/components/shortcut_viewer/public/cpp:manifest",
     "//ash/components/shortcut_viewer/public/mojom",
@@ -1536,7 +1533,6 @@
     "accessibility/touch_accessibility_enabler_unittest.cc",
     "accessibility/touch_exploration_controller_unittest.cc",
     "accessibility/touch_exploration_manager_unittest.cc",
-    "app_launch_unittest.cc",
     "app_list/app_list_controller_impl_unittest.cc",
     "app_list/app_list_presenter_delegate_unittest.cc",
     "app_list/app_list_unittest.cc",
@@ -1846,8 +1842,6 @@
     "//ash/assistant/util",
     "//ash/components/fast_ink",
     "//ash/components/fast_ink:unit_tests",
-    "//ash/components/quick_launch/public/cpp:manifest",
-    "//ash/components/quick_launch/public/mojom:constants",
     "//ash/components/shortcut_viewer:unit_tests",
     "//ash/components/tap_visualizer:unit_tests",
     "//ash/keyboard/arc",
@@ -1947,7 +1941,6 @@
     ":ash_service_resources",
     ":ash_service",
     "//ash/resources:ash_test_resources_100_percent",
-    "//ash/components/quick_launch:quick_launch_app",
   ]
 
   data = [
diff --git a/ash/DEPS b/ash/DEPS
index 630c52c..b96c386 100644
--- a/ash/DEPS
+++ b/ash/DEPS
@@ -47,7 +47,7 @@
   "-ash/components",
 
   # Ash can talk to public interfaces for mini-apps.
-  "+ash/components/quick_launch/public",
+  "+ash/components/shortcut_viewer/public",
   "+ash/components/tap_visualizer/public",
 
   # Ash sits above content. Exceptions live in //ash/content.
@@ -101,9 +101,6 @@
 ]
 
 specific_include_rules = {
-  "app_launch_unittest.cc": [
-    "+ash/components/quick_launch/public",
-  ],
   "ash_service\.*": [
     "+chromeos/cryptohome",
   ],
diff --git a/ash/accelerators/accelerator_controller.cc b/ash/accelerators/accelerator_controller.cc
index d92653c..afa0067 100644
--- a/ash/accelerators/accelerator_controller.cc
+++ b/ash/accelerators/accelerator_controller.cc
@@ -1258,7 +1258,6 @@
     case DEBUG_PRINT_LAYER_HIERARCHY:
     case DEBUG_PRINT_VIEW_HIERARCHY:
     case DEBUG_PRINT_WINDOW_HIERARCHY:
-    case DEBUG_SHOW_QUICK_LAUNCH:
     case DEBUG_SHOW_TOAST:
     case DEBUG_TOGGLE_DEVICE_SCALE_FACTOR:
     case DEBUG_TOGGLE_SHOW_DEBUG_BORDERS:
@@ -1426,7 +1425,6 @@
     case DEBUG_PRINT_LAYER_HIERARCHY:
     case DEBUG_PRINT_VIEW_HIERARCHY:
     case DEBUG_PRINT_WINDOW_HIERARCHY:
-    case DEBUG_SHOW_QUICK_LAUNCH:
     case DEBUG_SHOW_TOAST:
     case DEBUG_TOGGLE_DEVICE_SCALE_FACTOR:
       debug::PerformDebugActionIfEnabled(action);
diff --git a/ash/accelerators/accelerator_table.cc b/ash/accelerators/accelerator_table.cc
index 26180e4..2f63658 100644
--- a/ash/accelerators/accelerator_table.cc
+++ b/ash/accelerators/accelerator_table.cc
@@ -75,7 +75,6 @@
     {true, ui::VKEY_O, kDebugModifier, DEBUG_SHOW_TOAST},
     {true, ui::VKEY_P, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN,
      DEBUG_TOGGLE_TOUCH_PAD},
-    {true, ui::VKEY_Q, kDebugModifier, DEBUG_SHOW_QUICK_LAUNCH},
     {true, ui::VKEY_T, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN,
      DEBUG_TOGGLE_TOUCH_SCREEN},
     {true, ui::VKEY_T, kDebugModifier, DEBUG_TOGGLE_TABLET_MODE},
diff --git a/ash/accelerators/accelerator_table_unittest.cc b/ash/accelerators/accelerator_table_unittest.cc
index f8f13fd..dfc64d5 100644
--- a/ash/accelerators/accelerator_table_unittest.cc
+++ b/ash/accelerators/accelerator_table_unittest.cc
@@ -20,7 +20,7 @@
 constexpr int kNonSearchAcceleratorsNum = 89;
 // The hash of non-Search-based accelerators. See HashAcceleratorData().
 constexpr char kNonSearchAcceleratorsHash[] =
-    "bb35892635c794d78a20ff5f8a051aab";
+    "f0c17f9386d02edfa1fb4d9c5f8d2abe";
 
 struct Cmp {
   bool operator()(const AcceleratorData& lhs,
diff --git a/ash/accelerators/accelerator_unittest.cc b/ash/accelerators/accelerator_unittest.cc
index 9241056..caa5f73 100644
--- a/ash/accelerators/accelerator_unittest.cc
+++ b/ash/accelerators/accelerator_unittest.cc
@@ -12,6 +12,8 @@
 #include "ash/test/ash_test_base.h"
 #include "ash/test/ui_controls_factory_ash.h"
 #include "ash/test_screenshot_delegate.h"
+#include "ash/wm/overview/overview_controller.h"
+#include "ash/wm/overview/overview_observer.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
 #include "base/run_loop.h"
@@ -57,7 +59,7 @@
 // make sure they work properly. The test is done as an interactive ui test
 // using ui_controls::Send*() functions.
 // This is to catch any future regressions (crbug.com/469235).
-class AcceleratorTest : public AshTestBase, public ShellObserver {
+class AcceleratorTest : public AshTestBase, public OverviewObserver {
  public:
   AcceleratorTest() : is_in_overview_mode_(false) {}
 
@@ -66,7 +68,7 @@
 
     AshTestBase::SetUp();
 
-    Shell::Get()->AddShellObserver(this);
+    Shell::Get()->overview_controller()->AddObserver(this);
 
     chromeos::NetworkHandler::Initialize();
   }
@@ -74,7 +76,7 @@
   void TearDown() override {
     chromeos::NetworkHandler::Shutdown();
 
-    Shell::Get()->RemoveShellObserver(this);
+    Shell::Get()->overview_controller()->RemoveObserver(this);
 
     AshTestBase::TearDown();
 
@@ -94,7 +96,7 @@
     loop.Run();
   }
 
-  // ash::ShellObserver:
+  // OverviewObserver:
   void OnOverviewModeStarting() override { is_in_overview_mode_ = true; }
   void OnOverviewModeEnded() override { is_in_overview_mode_ = false; }
 
diff --git a/ash/accelerators/debug_commands.cc b/ash/accelerators/debug_commands.cc
index cffe099..f0a4f39 100644
--- a/ash/accelerators/debug_commands.cc
+++ b/ash/accelerators/debug_commands.cc
@@ -5,7 +5,6 @@
 #include "ash/accelerators/debug_commands.h"
 
 #include "ash/accelerators/accelerator_commands.h"
-#include "ash/components/quick_launch/public/mojom/constants.mojom.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
@@ -147,12 +146,6 @@
   }
 }
 
-void HandleShowQuickLaunch() {
-  // TODO(https://crbug.com/904148): This should not use |WarmService()|.
-  Shell::Get()->connector()->WarmService(service_manager::ServiceFilter::ByName(
-      quick_launch::mojom::kServiceName));
-}
-
 gfx::ImageSkia CreateWallpaperImage(SkColor fill, SkColor rect) {
   // TODO(oshima): Consider adding a command line option to control wallpaper
   // images for testing. The size is randomly picked.
@@ -221,7 +214,7 @@
 }
 
 void HandleTriggerCrash() {
-  CHECK(false) << "Intentional crash via debug accelerator.";
+  LOG(FATAL) << "Intentional crash via debug accelerator.";
 }
 
 }  // namespace
@@ -259,9 +252,6 @@
     case DEBUG_PRINT_WINDOW_HIERARCHY:
       HandlePrintWindowHierarchy();
       break;
-    case DEBUG_SHOW_QUICK_LAUNCH:
-      HandleShowQuickLaunch();
-      break;
     case DEBUG_SHOW_TOAST:
       Shell::Get()->toast_manager()->Show(
           ToastData("id", base::ASCIIToUTF16("Toast"), 5000 /* duration_ms */,
diff --git a/ash/app_launch_unittest.cc b/ash/app_launch_unittest.cc
deleted file mode 100644
index 9ded97f..0000000
--- a/ash/app_launch_unittest.cc
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2016 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/components/quick_launch/public/cpp/manifest.h"
-#include "ash/components/quick_launch/public/mojom/constants.mojom.h"
-#include "ash/public/cpp/manifest.h"
-#include "ash/public/cpp/test_manifest.h"
-#include "ash/public/interfaces/constants.mojom.h"
-#include "base/bind.h"
-#include "base/command_line.h"
-#include "base/run_loop.h"
-#include "base/test/scoped_task_environment.h"
-#include "services/service_manager/public/cpp/manifest_builder.h"
-#include "services/service_manager/public/cpp/test/test_service.h"
-#include "services/service_manager/public/cpp/test/test_service_manager.h"
-#include "services/ws/public/mojom/constants.mojom.h"
-#include "services/ws/public/mojom/window_server_test.mojom.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/base/ui_base_features.h"
-#include "ui/views/layout/layout_provider.h"
-
-namespace ash {
-
-const char kTestServiceName[] = "ash_unittests";
-
-void RunCallback(bool* success, base::RepeatingClosure callback, bool result) {
-  *success = result;
-  std::move(callback).Run();
-}
-
-class AppLaunchTest : public testing::Test {
- public:
-  AppLaunchTest()
-      : test_service_manager_(
-            {service_manager::Manifest(GetManifest())
-                 .Amend(GetManifestOverlayForTesting()),
-             quick_launch::GetManifest(),
-             service_manager::ManifestBuilder()
-                 .WithServiceName(kTestServiceName)
-                 .RequireCapability(mojom::kServiceName, "")
-                 .RequireCapability(quick_launch::mojom::kServiceName, "")
-                 .RequireCapability(ws::mojom::kServiceName, "test")
-                 .Build()}),
-        test_service_(
-            test_service_manager_.RegisterTestInstance(kTestServiceName)) {}
-  ~AppLaunchTest() override = default;
-
- protected:
-  service_manager::Connector* connector() { return test_service_.connector(); }
-
- private:
-  void SetUp() override {
-    base::CommandLine::ForCurrentProcess()->AppendSwitch("use-test-config");
-  }
-
-  base::test::ScopedTaskEnvironment task_environment_;
-  service_manager::TestServiceManager test_service_manager_;
-  service_manager::TestService test_service_;
-  views::LayoutProvider layout_provider_;
-
-  DISALLOW_COPY_AND_ASSIGN(AppLaunchTest);
-};
-
-TEST_F(AppLaunchTest, TestQuickLaunch) {
-  // This test launches ash in a separate service. That doesn't make sense with
-  // SingleProcessMash.
-  if (features::IsSingleProcessMash())
-    return;
-
-  // TODO(https://crbug.com/904148): These should not use |WarmService()|.
-  connector()->WarmService(
-      service_manager::ServiceFilter::ByName(mojom::kServiceName));
-  connector()->WarmService(service_manager::ServiceFilter::ByName(
-      quick_launch::mojom::kServiceName));
-
-  ws::mojom::WindowServerTestPtr test_interface;
-  connector()->BindInterface(
-      service_manager::ServiceFilter::ByName(ws::mojom::kServiceName),
-      mojo::MakeRequest(&test_interface));
-
-  base::RunLoop run_loop;
-  bool success = false;
-  test_interface->EnsureClientHasDrawnWindow(
-      quick_launch::mojom::kServiceName,
-      base::BindOnce(&RunCallback, &success, run_loop.QuitClosure()));
-  run_loop.Run();
-  EXPECT_TRUE(success);
-}
-
-}  // namespace ash
diff --git a/ash/app_list/BUILD.gn b/ash/app_list/BUILD.gn
index 7ff91c4..d8a3a47 100644
--- a/ash/app_list/BUILD.gn
+++ b/ash/app_list/BUILD.gn
@@ -40,6 +40,8 @@
     "views/apps_grid_view.cc",
     "views/apps_grid_view.h",
     "views/apps_grid_view_folder_delegate.h",
+    "views/assistant/assistant_main_stage.cc",
+    "views/assistant/assistant_main_stage.h",
     "views/assistant/assistant_main_view.cc",
     "views/assistant/assistant_main_view.h",
     "views/assistant/assistant_page_view.cc",
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index 2954c36..b100955 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -78,13 +78,14 @@
   Shell::Get()->tablet_mode_controller()->AddObserver(this);
   Shell::Get()->wallpaper_controller()->AddObserver(this);
   Shell::Get()->AddShellObserver(this);
+  Shell::Get()->overview_controller()->AddObserver(this);
   keyboard::KeyboardController::Get()->AddObserver(this);
   Shell::Get()->voice_interaction_controller()->AddLocalObserver(this);
   Shell::Get()->window_tree_host_manager()->AddObserver(this);
   Shell::Get()->mru_window_tracker()->AddObserver(this);
 }
 
-AppListControllerImpl::~AppListControllerImpl() {}
+AppListControllerImpl::~AppListControllerImpl() = default;
 
 void AppListControllerImpl::SetClient(mojom::AppListClientPtr client_ptr) {
   client_ = std::move(client_ptr);
@@ -496,6 +497,21 @@
   bindings_.FlushForTesting();
 }
 
+// Stop observing at the beginning of ~Shell to avoid unnecessary work during
+// Shell shutdown.
+void AppListControllerImpl::OnShellDestroying() {
+  Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
+  keyboard::KeyboardController::Get()->RemoveObserver(this);
+  Shell::Get()->RemoveShellObserver(this);
+  Shell::Get()->wallpaper_controller()->RemoveObserver(this);
+  Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
+  Shell::Get()->overview_controller()->RemoveObserver(this);
+  Shell::Get()->session_controller()->RemoveObserver(this);
+  Shell::Get()->voice_interaction_controller()->RemoveLocalObserver(this);
+  Shell::Get()->mru_window_tracker()->RemoveObserver(this);
+  model_.RemoveObserver(this);
+}
+
 void AppListControllerImpl::OnOverviewModeStarting() {
   if (!IsTabletMode()) {
     DismissAppList();
@@ -536,20 +552,6 @@
                                            use_slide_to_exit_overview_);
 }
 
-// Stop observing at the beginning of ~Shell to avoid unnecessary work during
-// Shell shutdown.
-void AppListControllerImpl::OnShellDestroying() {
-  Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
-  keyboard::KeyboardController::Get()->RemoveObserver(this);
-  Shell::Get()->RemoveShellObserver(this);
-  Shell::Get()->wallpaper_controller()->RemoveObserver(this);
-  Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
-  Shell::Get()->session_controller()->RemoveObserver(this);
-  Shell::Get()->voice_interaction_controller()->RemoveLocalObserver(this);
-  Shell::Get()->mru_window_tracker()->RemoveObserver(this);
-  model_.RemoveObserver(this);
-}
-
 void AppListControllerImpl::OnTabletModeStarted() {
   if (presenter_.GetTargetVisibility()) {
     DCHECK(IsVisible());
diff --git a/ash/app_list/app_list_controller_impl.h b/ash/app_list/app_list_controller_impl.h
index 6a616ba..ce85fc7 100644
--- a/ash/app_list/app_list_controller_impl.h
+++ b/ash/app_list/app_list_controller_impl.h
@@ -26,6 +26,7 @@
 #include "ash/shell_observer.h"
 #include "ash/wallpaper/wallpaper_controller_observer.h"
 #include "ash/wm/mru_window_tracker.h"
+#include "ash/wm/overview/overview_observer.h"
 #include "ash/wm/tablet_mode/tablet_mode_observer.h"
 #include "base/observer_list.h"
 #include "components/sync/model/string_ordinal.h"
@@ -51,6 +52,7 @@
       public app_list::AppListModelObserver,
       public app_list::AppListViewDelegate,
       public ash::ShellObserver,
+      public OverviewObserver,
       public TabletModeObserver,
       public keyboard::KeyboardControllerObserver,
       public WallpaperControllerObserver,
@@ -205,10 +207,12 @@
   void FlushForTesting();
 
   // ShellObserver:
+  void OnShellDestroying() override;
+
+  // OverviewObserver:
   void OnOverviewModeStarting() override;
   void OnOverviewModeEnding(OverviewSession* overview_session) override;
   void OnOverviewModeEndingAnimationComplete(bool canceled) override;
-  void OnShellDestroying() override;
 
   // TabletModeObserver:
   void OnTabletModeStarted() override;
diff --git a/ash/app_list/views/assistant/assistant_main_stage.cc b/ash/app_list/views/assistant/assistant_main_stage.cc
new file mode 100644
index 0000000..a79d501
--- /dev/null
+++ b/ash/app_list/views/assistant/assistant_main_stage.cc
@@ -0,0 +1,348 @@
+// Copyright 2019 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/assistant/assistant_main_stage.h"
+
+#include "ash/assistant/model/assistant_query.h"
+#include "ash/assistant/ui/assistant_view_delegate.h"
+#include "ash/assistant/ui/main_stage/assistant_footer_view.h"
+#include "ash/assistant/ui/main_stage/assistant_progress_indicator.h"
+#include "ash/assistant/ui/main_stage/assistant_query_view.h"
+#include "ash/assistant/ui/main_stage/ui_element_container_view.h"
+#include "ash/assistant/util/animation_util.h"
+#include "ash/assistant/util/assistant_util.h"
+#include "base/bind.h"
+#include "base/time/time.h"
+#include "ui/compositor/callback_layer_animation_observer.h"
+#include "ui/compositor/layer_animation_element.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/border.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/layout/layout_manager.h"
+
+namespace app_list {
+
+namespace {
+
+// Footer animation.
+constexpr int kFooterAnimationTranslationDip = 22;
+constexpr base::TimeDelta kFooterAnimationTranslationDelay =
+    base::TimeDelta::FromMilliseconds(66);
+constexpr base::TimeDelta kFooterAnimationTranslationDuration =
+    base::TimeDelta::FromMilliseconds(416);
+constexpr base::TimeDelta kFooterAnimationFadeInDelay =
+    base::TimeDelta::FromMilliseconds(149);
+constexpr base::TimeDelta kFooterAnimationFadeInDuration =
+    base::TimeDelta::FromMilliseconds(250);
+constexpr base::TimeDelta kFooterAnimationFadeOutDuration =
+    base::TimeDelta::FromMilliseconds(100);
+
+// Footer entry animation.
+constexpr base::TimeDelta kFooterEntryAnimationFadeInDelay =
+    base::TimeDelta::FromMilliseconds(283);
+constexpr base::TimeDelta kFooterEntryAnimationFadeInDuration =
+    base::TimeDelta::FromMilliseconds(167);
+
+// Progress animation.
+constexpr base::TimeDelta kProgressAnimationFadeInDelay =
+    base::TimeDelta::FromMilliseconds(233);
+constexpr base::TimeDelta kProgressAnimationFadeInDuration =
+    base::TimeDelta::FromMilliseconds(167);
+
+// HorizontalSeparator ---------------------------------------------------------
+
+// A horizontal line to separate the dialog plate.
+class HorizontalSeparator : public views::View {
+ public:
+  explicit HorizontalSeparator(int preferred_width, int preferred_height)
+      : preferred_width_(preferred_width),
+        preferred_height_(preferred_height) {}
+
+  ~HorizontalSeparator() override = default;
+
+  // views::View overrides:
+  const char* GetClassName() const override { return "HorizontalSeparator"; }
+
+  gfx::Size CalculatePreferredSize() const override {
+    return gfx::Size(preferred_width_, preferred_height_);
+  }
+
+  void OnPaint(gfx::Canvas* canvas) override {
+    constexpr SkColor kSeparatorColor = SkColorSetA(SK_ColorBLACK, 0x0F);
+    constexpr int kSeparatorThicknessDip = 2;
+    gfx::Rect draw_bounds(GetContentsBounds());
+    // TODO(wutao): To be finalized.
+    const int inset_height =
+        (draw_bounds.height() - kSeparatorThicknessDip) / 2;
+    draw_bounds.Inset(0, inset_height);
+    canvas->FillRect(draw_bounds, kSeparatorColor);
+    View::OnPaint(canvas);
+  }
+
+ private:
+  const int preferred_width_;
+  const int preferred_height_;
+
+  DISALLOW_COPY_AND_ASSIGN(HorizontalSeparator);
+};
+
+}  // namespace
+
+// AssistantMainStage ----------------------------------------------------------
+
+AssistantMainStage::AssistantMainStage(ash::AssistantViewDelegate* delegate)
+    : delegate_(delegate),
+      footer_animation_observer_(
+          std::make_unique<ui::CallbackLayerAnimationObserver>(
+              /*animation_started_callback=*/base::BindRepeating(
+                  &AssistantMainStage::OnFooterAnimationStarted,
+                  base::Unretained(this)),
+              /*animation_ended_callback=*/base::BindRepeating(
+                  &AssistantMainStage::OnFooterAnimationEnded,
+                  base::Unretained(this)))) {
+  InitLayout();
+
+  // The view hierarchy will be destructed before |assistant_controller_| in
+  // Shell, which owns AssistantViewDelegate, so AssistantViewDelegate is
+  // guaranteed to outlive the AssistantMainStage.
+  delegate_->AddInteractionModelObserver(this);
+  delegate_->AddUiModelObserver(this);
+}
+
+AssistantMainStage::~AssistantMainStage() {
+  delegate_->RemoveUiModelObserver(this);
+  delegate_->RemoveInteractionModelObserver(this);
+}
+
+const char* AssistantMainStage::GetClassName() const {
+  return "AssistantMainStage";
+}
+
+void AssistantMainStage::ChildPreferredSizeChanged(views::View* child) {
+  PreferredSizeChanged();
+}
+
+void AssistantMainStage::OnViewPreferredSizeChanged(views::View* view) {
+  PreferredSizeChanged();
+}
+
+void AssistantMainStage::InitLayout() {
+  views::BoxLayout* layout =
+      SetLayoutManager(std::make_unique<views::BoxLayout>(
+          views::BoxLayout::Orientation::kVertical));
+  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+  layout->set_cross_axis_alignment(
+      views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER);
+
+  // The children of AssistantMainStage will be animated on their own layers and
+  // we want them to be clipped by their parent layer.
+  SetPaintToLayer();
+  layer()->SetFillsBoundsOpaquely(false);
+  layer()->SetMasksToBounds(true);
+
+  // TODO(wutao): finalize the padding.
+  constexpr int kTopPaddingDip = 3;
+  SetBorder(views::CreateEmptyBorder(gfx::Insets(kTopPaddingDip, 0, 0, 0)));
+
+  // Separators: the progress indicator and the horizontal separator will be the
+  // separator when querying and showing the results, respectively. The height
+  // of the horizontal separator is set to be the same as the progress indicator
+  // in order to avoid height changes and relayout.
+  // Progress indicator, which will be animated on its own layer.
+  progress_indicator_ = new ash::AssistantProgressIndicator();
+  progress_indicator_->SetPaintToLayer();
+  progress_indicator_->layer()->SetFillsBoundsOpaquely(false);
+  progress_indicator_->SetVisible(false);
+  AddChildView(progress_indicator_);
+
+  // Horizontal separator.
+  // TODO(wutao): finalize the width.
+  constexpr int kSeparatorWidthDip = 64;
+  horizontal_separator_ = new HorizontalSeparator(
+      kSeparatorWidthDip, progress_indicator_->GetPreferredSize().height());
+  AddChildView(horizontal_separator_);
+
+  // Query view.
+  query_view_ = new ash::AssistantQueryView();
+  AddChildView(query_view_);
+
+  // UI element container.
+  ui_element_container_ = new ash::UiElementContainerView(delegate_);
+  AddChildView(ui_element_container_);
+
+  layout->SetFlexForView(ui_element_container_, 1,
+                         /*use_min_size=*/true);
+
+  // Footer.
+  // Note that the |footer_| is placed within its own view container so that as
+  // its visibility changes, its parent container will still reserve the same
+  // layout space. This prevents jank that would otherwise occur due to
+  // |ui_element_container_| claiming that empty space.
+  views::View* footer_container = new views::View();
+  footer_container->SetLayoutManager(std::make_unique<views::FillLayout>());
+
+  footer_ = new ash::AssistantFooterView(delegate_);
+  footer_->AddObserver(this);
+
+  // The footer will be animated on its own layer.
+  footer_->SetPaintToLayer();
+  footer_->layer()->SetFillsBoundsOpaquely(false);
+
+  footer_container->AddChildView(footer_);
+  AddChildView(footer_container);
+}
+
+void AssistantMainStage::OnCommittedQueryChanged(
+    const ash::AssistantQuery& query) {
+  using ash::assistant::util::CreateLayerAnimationSequence;
+  using ash::assistant::util::CreateOpacityElement;
+
+  // TODO(wutao): Replace the visibility change by animations.
+  horizontal_separator_->SetVisible(false);
+
+  // Show the progress indicator.
+  progress_indicator_->SetVisible(true);
+  progress_indicator_->layer()->GetAnimator()->StartAnimation(
+      CreateLayerAnimationSequence(
+          // Delay...
+          ui::LayerAnimationElement::CreatePauseElement(
+              ui::LayerAnimationElement::AnimatableProperty::OPACITY,
+              kProgressAnimationFadeInDelay),
+          // ...then fade in.
+          ash::assistant::util::CreateOpacityElement(
+              1.f, kProgressAnimationFadeInDuration)));
+
+  // Update the view.
+  query_view_->SetQuery(query);
+}
+
+void AssistantMainStage::OnPendingQueryChanged(
+    const ash::AssistantQuery& query) {
+  query_view_->SetQuery(query);
+  UpdateFooter();
+}
+
+void AssistantMainStage::OnPendingQueryCleared() {
+  UpdateFooter();
+}
+
+void AssistantMainStage::OnResponseChanged(
+    const std::shared_ptr<ash::AssistantResponse>& response) {
+  // TODO(wutao): Replace the visibility change by animations.
+  horizontal_separator_->SetVisible(true);
+  progress_indicator_->SetVisible(false);
+
+  UpdateFooter();
+}
+
+void AssistantMainStage::OnUiVisibilityChanged(
+    ash::AssistantVisibility new_visibility,
+    ash::AssistantVisibility old_visibility,
+    base::Optional<ash::AssistantEntryPoint> entry_point,
+    base::Optional<ash::AssistantExitPoint> exit_point) {
+  if (ash::assistant::util::IsStartingSession(new_visibility, old_visibility)) {
+    // When Assistant is starting a new session, we animate in the appearance of
+    // the footer.
+    using ash::assistant::util::CreateLayerAnimationSequence;
+    using ash::assistant::util::CreateOpacityElement;
+    using ash::assistant::util::CreateTransformElement;
+
+    // Set up our pre-animation values.
+    footer_->layer()->SetOpacity(0.f);
+
+    // Animate the footer to 100% opacity with delay.
+    footer_->layer()->GetAnimator()->StartAnimation(
+        CreateLayerAnimationSequence(
+            ui::LayerAnimationElement::CreatePauseElement(
+                ui::LayerAnimationElement::AnimatableProperty::OPACITY,
+                kFooterEntryAnimationFadeInDelay),
+            CreateOpacityElement(1.f, kFooterEntryAnimationFadeInDuration)));
+    return;
+  }
+
+  if (!ash::assistant::util::IsFinishingSession(new_visibility))
+    return;
+
+  progress_indicator_->layer()->SetOpacity(0.f);
+  progress_indicator_->layer()->SetTransform(gfx::Transform());
+
+  UpdateFooter();
+}
+
+void AssistantMainStage::UpdateFooter() {
+  using ash::assistant::util::CreateLayerAnimationSequence;
+  using ash::assistant::util::CreateOpacityElement;
+  using ash::assistant::util::CreateTransformElement;
+  using ash::assistant::util::StartLayerAnimationSequence;
+  using ash::assistant::util::StartLayerAnimationSequencesTogether;
+
+  // The footer is only visible when the progress indicator is not.
+  // When it is not visible, it should not process events.
+  bool visible = !progress_indicator_->visible();
+
+  // Reset visibility to enable animation.
+  footer_->SetVisible(true);
+
+  if (visible) {
+    // The footer will animate up into position so we need to set an initial
+    // offset transformation from which to animate.
+    gfx::Transform transform;
+    transform.Translate(0, kFooterAnimationTranslationDip);
+    footer_->layer()->SetTransform(transform);
+
+    // Animate the entry of the footer.
+    StartLayerAnimationSequencesTogether(
+        footer_->layer()->GetAnimator(),
+        {// Animate the translation with delay.
+         CreateLayerAnimationSequence(
+             ui::LayerAnimationElement::CreatePauseElement(
+                 ui::LayerAnimationElement::AnimatableProperty::TRANSFORM,
+                 kFooterAnimationTranslationDelay),
+             CreateTransformElement(gfx::Transform(),
+                                    kFooterAnimationTranslationDuration,
+                                    gfx::Tween::Type::FAST_OUT_SLOW_IN_2)),
+         // Animate the fade in with delay.
+         CreateLayerAnimationSequence(
+             ui::LayerAnimationElement::CreatePauseElement(
+                 ui::LayerAnimationElement::AnimatableProperty::OPACITY,
+                 kFooterAnimationFadeInDelay),
+             CreateOpacityElement(1.f, kFooterAnimationFadeInDuration))},
+        // Observer animation start/end events.
+        footer_animation_observer_.get());
+  } else {
+    // Animate the exit of the footer.
+    StartLayerAnimationSequence(
+        footer_->layer()->GetAnimator(),
+        // Animate fade out.
+        CreateLayerAnimationSequence(
+            CreateOpacityElement(0.f, kFooterAnimationFadeOutDuration)),
+        // Observe animation start/end events.
+        footer_animation_observer_.get());
+  }
+
+  // Set the observer to active so that we'll receive start/end events.
+  footer_animation_observer_->SetActive();
+}
+
+void AssistantMainStage::OnFooterAnimationStarted(
+    const ui::CallbackLayerAnimationObserver& observer) {
+  // The footer should not process events while animating.
+  footer_->set_can_process_events_within_subtree(/*can_process=*/false);
+}
+
+bool AssistantMainStage::OnFooterAnimationEnded(
+    const ui::CallbackLayerAnimationObserver& observer) {
+  // The footer should only process events when visible. It is only visible when
+  // the progress indicator is not visible.
+  bool visible = !progress_indicator_->visible();
+  footer_->set_can_process_events_within_subtree(visible);
+  footer_->SetVisible(visible);
+
+  // Return false so that the observer does not destroy itself.
+  return false;
+}
+
+}  // namespace app_list
diff --git a/ash/app_list/views/assistant/assistant_main_stage.h b/ash/app_list/views/assistant/assistant_main_stage.h
new file mode 100644
index 0000000..87f3f11
--- /dev/null
+++ b/ash/app_list/views/assistant/assistant_main_stage.h
@@ -0,0 +1,91 @@
+// Copyright 2019 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_APP_LIST_VIEWS_ASSISTANT_ASSISTANT_MAIN_STAGE_H_
+#define ASH_APP_LIST_VIEWS_ASSISTANT_ASSISTANT_MAIN_STAGE_H_
+
+#include <memory>
+
+#include "ash/app_list/app_list_export.h"
+#include "ash/assistant/model/assistant_interaction_model_observer.h"
+#include "ash/assistant/model/assistant_ui_model_observer.h"
+#include "base/macros.h"
+#include "ui/views/view.h"
+#include "ui/views/view_observer.h"
+
+namespace ash {
+class AssistantFooterView;
+class AssistantProgressIndicator;
+class AssistantQueryView;
+class AssistantViewDelegate;
+class UiElementContainerView;
+}  // namespace ash
+
+namespace ui {
+class CallbackLayerAnimationObserver;
+}  // namespace ui
+
+namespace app_list {
+
+// AssistantMainStage is the child of AssistantMainView responsible for
+// displaying the Assistant interaction to the user. This includes visual
+// affordances for the query, response, as well as suggestions.
+class APP_LIST_EXPORT AssistantMainStage
+    : public views::View,
+      public views::ViewObserver,
+      public ash::AssistantInteractionModelObserver,
+      public ash::AssistantUiModelObserver {
+ public:
+  explicit AssistantMainStage(ash::AssistantViewDelegate* delegate);
+  ~AssistantMainStage() override;
+
+  // views::View:
+  const char* GetClassName() const override;
+  void ChildPreferredSizeChanged(views::View* child) override;
+
+  // views::ViewObserver:
+  void OnViewPreferredSizeChanged(views::View* view) override;
+
+  // AssistantInteractionModelObserver:
+  void OnCommittedQueryChanged(const ash::AssistantQuery& query) override;
+  void OnPendingQueryChanged(const ash::AssistantQuery& query) override;
+  void OnPendingQueryCleared() override;
+  void OnResponseChanged(
+      const std::shared_ptr<ash::AssistantResponse>& response) override;
+
+  // AssistantUiModelObserver:
+  void OnUiVisibilityChanged(
+      ash::AssistantVisibility new_visibility,
+      ash::AssistantVisibility old_visibility,
+      base::Optional<ash::AssistantEntryPoint> entry_point,
+      base::Optional<ash::AssistantExitPoint> exit_point) override;
+
+ private:
+  void InitLayout();
+
+  void UpdateFooter();
+
+  void OnFooterAnimationStarted(
+      const ui::CallbackLayerAnimationObserver& observer);
+  bool OnFooterAnimationEnded(
+      const ui::CallbackLayerAnimationObserver& observer);
+
+  ash::AssistantViewDelegate* const delegate_;  // Owned by Shell.
+
+  // Owned by view hierarchy.
+  ash::AssistantProgressIndicator* progress_indicator_;
+  views::View* horizontal_separator_;
+  ash::AssistantQueryView* query_view_;
+  ash::UiElementContainerView* ui_element_container_;
+  ash::AssistantFooterView* footer_;
+
+  std::unique_ptr<ui::CallbackLayerAnimationObserver>
+      footer_animation_observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(AssistantMainStage);
+};
+
+}  // namespace app_list
+
+#endif  // ASH_APP_LIST_VIEWS_ASSISTANT_ASSISTANT_MAIN_STAGE_H_
diff --git a/ash/app_list/views/assistant/assistant_main_view.cc b/ash/app_list/views/assistant/assistant_main_view.cc
index 6a1d4c3..d6028b7 100644
--- a/ash/app_list/views/assistant/assistant_main_view.cc
+++ b/ash/app_list/views/assistant/assistant_main_view.cc
@@ -6,11 +6,11 @@
 
 #include <memory>
 
+#include "ash/app_list/views/assistant/assistant_main_stage.h"
 #include "ash/app_list/views/assistant/dialog_plate.h"
 #include "ash/assistant/ui/assistant_ui_constants.h"
 #include "ash/assistant/ui/assistant_view_delegate.h"
 #include "ash/assistant/ui/dialog_plate/dialog_plate.h"
-#include "ash/assistant/ui/main_stage/assistant_main_stage.h"
 #include "ui/views/layout/box_layout.h"
 
 namespace app_list {
@@ -83,7 +83,7 @@
   AddChildView(dialog_plate_);
 
   // Main stage.
-  main_stage_ = new ash::AssistantMainStage(delegate_);
+  main_stage_ = new AssistantMainStage(delegate_);
   AddChildView(main_stage_);
 
   layout->SetFlexForView(main_stage_, 1);
diff --git a/ash/app_list/views/assistant/assistant_main_view.h b/ash/app_list/views/assistant/assistant_main_view.h
index 5898f7c..9a9534c 100644
--- a/ash/app_list/views/assistant/assistant_main_view.h
+++ b/ash/app_list/views/assistant/assistant_main_view.h
@@ -10,12 +10,12 @@
 #include "ui/views/view.h"
 
 namespace ash {
-class AssistantMainStage;
 class AssistantViewDelegate;
 }  // namespace ash
 
 namespace app_list {
 
+class AssistantMainStage;
 class DialogPlate;
 
 class APP_LIST_EXPORT AssistantMainView : public views::View {
@@ -39,7 +39,7 @@
   ash::AssistantViewDelegate* const delegate_;
 
   DialogPlate* dialog_plate_;            // Owned by view hierarchy.
-  ash::AssistantMainStage* main_stage_;  // Owned by view hierarchy.
+  AssistantMainStage* main_stage_;       // Owned by view hierarchy.
 
   DISALLOW_COPY_AND_ASSIGN(AssistantMainView);
 };
diff --git a/ash/components/quick_launch/BUILD.gn b/ash/components/quick_launch/BUILD.gn
deleted file mode 100644
index ea314eb..0000000
--- a/ash/components/quick_launch/BUILD.gn
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2016 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.
-
-import("//build/config/ui.gni")
-import("//mojo/public/tools/bindings/mojom.gni")
-import("//services/service_manager/public/cpp/service_executable.gni")
-import("//tools/grit/repack.gni")
-
-source_set("lib") {
-  sources = [
-    "quick_launch_application.cc",
-    "quick_launch_application.h",
-  ]
-
-  deps = [
-    "//ash/public/cpp",
-    "//base",
-    "//mash/public/mojom",
-    "//mojo/public/cpp/bindings",
-    "//services/catalog/public/mojom",
-    "//services/service_manager/public/cpp",
-    "//services/service_manager/public/mojom",
-    "//ui/views",
-    "//ui/views/mus:for_mojo_application",
-    "//url",
-  ]
-}
-
-service_executable("quick_launch_app") {
-  sources = [
-    "main.cc",
-  ]
-
-  deps = [
-    ":lib",
-    "//services/service_manager/public/cpp",
-    "//ui/views/mus:for_mojo_application",
-  ]
-
-  data_deps = [
-    "//ui/views/mus:resources",
-  ]
-}
diff --git a/ash/components/quick_launch/DEPS b/ash/components/quick_launch/DEPS
deleted file mode 100644
index bac08dc..0000000
--- a/ash/components/quick_launch/DEPS
+++ /dev/null
@@ -1,6 +0,0 @@
-include_rules = [
-  "+mash/public",
-  "+services/catalog/public",
-  "+services/ws/public",
-  "+url",
-]
diff --git a/ash/components/quick_launch/main.cc b/ash/components/quick_launch/main.cc
deleted file mode 100644
index ea494d4..0000000
--- a/ash/components/quick_launch/main.cc
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2016 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/components/quick_launch/quick_launch_application.h"
-#include "base/message_loop/message_loop.h"
-#include "services/service_manager/public/cpp/service_executable/service_main.h"
-
-void ServiceMain(service_manager::mojom::ServiceRequest request) {
-  base::MessageLoop message_loop;
-  quick_launch::QuickLaunchApplication service(std::move(request));
-  service.set_running_standalone(true);
-  service.RunUntilTermination();
-}
diff --git a/ash/components/quick_launch/public/cpp/BUILD.gn b/ash/components/quick_launch/public/cpp/BUILD.gn
deleted file mode 100644
index a331858..0000000
--- a/ash/components/quick_launch/public/cpp/BUILD.gn
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2019 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.
-
-source_set("manifest") {
-  sources = [
-    "manifest.cc",
-    "manifest.h",
-  ]
-
-  deps = [
-    "//ash/components/quick_launch/public/mojom",
-    "//base",
-    "//services/service_manager/public/cpp",
-    "//services/ws/public/mojom:constants",
-  ]
-}
diff --git a/ash/components/quick_launch/public/cpp/OWNERS b/ash/components/quick_launch/public/cpp/OWNERS
deleted file mode 100644
index 6faeaa4..0000000
--- a/ash/components/quick_launch/public/cpp/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-per-file manifest.cc=set noparent
-per-file manifest.cc=file://ipc/SECURITY_OWNERS
-per-file manifest.h=set noparent
-per-file manifest.h=file://ipc/SECURITY_OWNERS
diff --git a/ash/components/quick_launch/public/cpp/manifest.cc b/ash/components/quick_launch/public/cpp/manifest.cc
deleted file mode 100644
index 80245b6..0000000
--- a/ash/components/quick_launch/public/cpp/manifest.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2019 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/components/quick_launch/public/cpp/manifest.h"
-
-#include "ash/components/quick_launch/public/mojom/constants.mojom.h"
-#include "base/no_destructor.h"
-#include "services/service_manager/public/cpp/manifest_builder.h"
-#include "services/ws/public/mojom/constants.mojom.h"
-
-namespace quick_launch {
-
-const service_manager::Manifest& GetManifest() {
-  static base::NoDestructor<service_manager::Manifest> manifest{
-      service_manager::ManifestBuilder()
-          .WithServiceName(mojom::kServiceName)
-          .WithDisplayName("Quick Launch Bar")
-          .WithOptions(service_manager::ManifestOptionsBuilder()
-                           .WithSandboxType("none")
-                           .Build())
-          .RequireCapability(ws::mojom::kServiceName, "app")
-          .RequireCapability("catalog", "catalog:catalog")
-          .RequireCapability("catalog", "directory")
-          .RequireCapability("*", "mash:launchable")
-          .Build()};
-  return *manifest;
-}
-}  // namespace quick_launch
diff --git a/ash/components/quick_launch/public/cpp/manifest.h b/ash/components/quick_launch/public/cpp/manifest.h
deleted file mode 100644
index a9b9003..0000000
--- a/ash/components/quick_launch/public/cpp/manifest.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2019 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_COMPONENTS_QUICK_LAUNCH_PUBLIC_CPP_MANIFEST_H_
-#define ASH_COMPONENTS_QUICK_LAUNCH_PUBLIC_CPP_MANIFEST_H_
-
-#include "services/service_manager/public/cpp/manifest.h"
-
-namespace quick_launch {
-
-const service_manager::Manifest& GetManifest();
-
-}  // namespace quick_launch
-
-#endif  // ASH_COMPONENTS_QUICK_LAUNCH_PUBLIC_CPP_MANIFEST_H_
diff --git a/ash/components/quick_launch/public/mojom/BUILD.gn b/ash/components/quick_launch/public/mojom/BUILD.gn
deleted file mode 100644
index a6194cf..0000000
--- a/ash/components/quick_launch/public/mojom/BUILD.gn
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2016 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.
-
-import("//mojo/public/tools/bindings/mojom.gni")
-
-group("mojom") {
-  public_deps = [
-    ":constants",
-  ]
-}
-
-mojom("constants") {
-  sources = [
-    "constants.mojom",
-  ]
-}
diff --git a/ash/components/quick_launch/public/mojom/constants.mojom b/ash/components/quick_launch/public/mojom/constants.mojom
deleted file mode 100644
index 46c3d318..0000000
--- a/ash/components/quick_launch/public/mojom/constants.mojom
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright 2016 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.
-
-module quick_launch.mojom;
-
-const string kServiceName = "quick_launch_app";
diff --git a/ash/components/quick_launch/quick_launch_application.cc b/ash/components/quick_launch/quick_launch_application.cc
deleted file mode 100644
index 9c5d30c..0000000
--- a/ash/components/quick_launch/quick_launch_application.cc
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright 2016 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/components/quick_launch/quick_launch_application.h"
-
-#include "ash/public/cpp/ash_client.h"
-#include "base/bind.h"
-#include "base/macros.h"
-#include "base/memory/ptr_util.h"
-#include "base/run_loop.h"
-#include "base/strings/string16.h"
-#include "base/strings/string_util.h"
-#include "base/strings/utf_string_conversions.h"
-#include "mash/public/mojom/launchable.mojom.h"
-#include "services/catalog/public/mojom/catalog.mojom.h"
-#include "services/catalog/public/mojom/constants.mojom.h"
-#include "services/service_manager/public/cpp/connector.h"
-#include "services/service_manager/public/cpp/service.h"
-#include "ui/aura/window.h"
-#include "ui/aura/window_tree_host.h"
-#include "ui/views/background.h"
-#include "ui/views/controls/textfield/textfield.h"
-#include "ui/views/controls/textfield/textfield_controller.h"
-#include "ui/views/mus/aura_init.h"
-#include "ui/views/widget/widget.h"
-#include "ui/views/widget/widget_delegate.h"
-#include "url/gurl.h"
-
-namespace quick_launch {
-namespace {
-
-class QuickLaunchUI : public views::WidgetDelegateView,
-                      public views::TextfieldController {
- public:
-  QuickLaunchUI(QuickLaunchApplication* quick_launch,
-                service_manager::Connector* connector,
-                catalog::mojom::CatalogPtr catalog)
-      : quick_launch_(quick_launch),
-        connector_(connector),
-        prompt_(new views::Textfield),
-        catalog_(std::move(catalog)) {
-    SetBackground(views::CreateStandardPanelBackground());
-    prompt_->set_controller(this);
-    AddChildView(prompt_);
-
-    UpdateEntries();
-  }
-  ~QuickLaunchUI() override { quick_launch_->Quit(); }
-
- private:
-  // Overridden from views::WidgetDelegate:
-  base::string16 GetWindowTitle() const override {
-    // TODO(beng): use resources.
-    return base::ASCIIToUTF16("QuickLaunch");
-  }
-
-  // Overridden from views::View:
-  void Layout() override {
-    gfx::Rect bounds = GetLocalBounds();
-    bounds.Inset(5, 5);
-    prompt_->SetBoundsRect(bounds);
-  }
-  gfx::Size CalculatePreferredSize() const override {
-    gfx::Size ps = prompt_->GetPreferredSize();
-    ps.Enlarge(500, 10);
-    return ps;
-  }
-
-  // Overridden from views::TextFieldController:
-  bool HandleKeyEvent(views::Textfield* sender,
-                      const ui::KeyEvent& key_event) override {
-    if (key_event.type() != ui::ET_KEY_PRESSED)
-      return false;
-
-    // The user didn't like our suggestion, don't make another until they
-    // type another character.
-    suggestion_rejected_ = key_event.key_code() == ui::VKEY_BACK ||
-                           key_event.key_code() == ui::VKEY_DELETE;
-    if (key_event.key_code() == ui::VKEY_RETURN) {
-      Launch(Canonicalize(prompt_->text()), key_event.IsControlDown());
-      prompt_->SetText(base::string16());
-      UpdateEntries();
-    }
-    return false;
-  }
-
-  void ContentsChanged(views::Textfield* sender,
-                       const base::string16& new_contents) override {
-    // Don't keep making a suggestion if the user didn't like what we offered.
-    if (suggestion_rejected_)
-      return;
-
-    if (new_contents.empty())
-      return;
-
-    // TODO(beng): it'd be nice if we persisted some history/scoring here.
-    for (const auto& name : app_names_) {
-      if (base::StartsWith(name, new_contents,
-                           base::CompareCase::INSENSITIVE_ASCII)) {
-        base::string16 suffix = name;
-        base::ReplaceSubstringsAfterOffset(&suffix, 0, new_contents,
-                                           base::string16());
-        gfx::Range range(static_cast<uint32_t>(new_contents.size()),
-                         static_cast<uint32_t>(name.size()));
-        prompt_->SetText(name);
-        prompt_->SelectRange(range);
-        break;
-      }
-    }
-  }
-
-  std::string Canonicalize(const base::string16& input) const {
-    base::string16 working;
-    base::TrimWhitespace(input, base::TRIM_ALL, &working);
-    GURL url(working);
-    if (url.scheme() != "service" && url.scheme() != "exe")
-      working = base::ASCIIToUTF16("") + working;
-    return base::UTF16ToUTF8(working);
-  }
-
-  void UpdateEntries() {
-    catalog_->GetEntriesProvidingCapability(
-        "mash:launchable",
-        base::BindRepeating(&QuickLaunchUI::OnGotCatalogEntries,
-                            base::Unretained(this)));
-  }
-
-  void OnGotCatalogEntries(std::vector<catalog::mojom::EntryPtr> entries) {
-    for (const auto& entry : entries)
-      app_names_.insert(base::UTF8ToUTF16(entry->name));
-  }
-
-  void Launch(const std::string& name, bool new_window) {
-    // TODO(jamescook): Start the service by name. Most services don't
-    // support the Launchable interface any more.
-    ::mash::mojom::LaunchablePtr launchable;
-    connector_->BindInterface(name, &launchable);
-    launchable->Launch(mash::mojom::kWindow,
-                       new_window ? mash::mojom::LaunchMode::MAKE_NEW
-                                  : mash::mojom::LaunchMode::REUSE);
-  }
-
-  QuickLaunchApplication* quick_launch_;
-  service_manager::Connector* connector_;
-  views::Textfield* prompt_;
-  catalog::mojom::CatalogPtr catalog_;
-  std::set<base::string16> app_names_;
-  bool suggestion_rejected_ = false;
-
-  DISALLOW_COPY_AND_ASSIGN(QuickLaunchUI);
-};
-
-}  // namespace
-
-QuickLaunchApplication::QuickLaunchApplication(
-    service_manager::mojom::ServiceRequest request)
-    : service_binding_(this, std::move(request)) {}
-
-QuickLaunchApplication::~QuickLaunchApplication() {
-  if (window_)
-    window_->CloseNow();
-}
-
-void QuickLaunchApplication::Quit() {
-  window_ = nullptr;
-  Terminate();
-}
-
-void QuickLaunchApplication::OnStart() {
-  // If AuraInit was unable to initialize there is no longer a peer connection.
-  // The ServiceManager is in the process of shutting down, however we haven't
-  // been notified yet. We just self-terminate in this case.
-  views::AuraInit::InitParams params;
-  params.connector = service_binding_.GetConnector();
-  params.identity = service_binding_.identity();
-  params.register_path_provider = running_standalone_;
-  params.use_accessibility_host = true;
-  aura_init_ = views::AuraInit::Create(params);
-  if (!aura_init_) {
-    Terminate();
-    return;
-  }
-
-  // Register as a client of the window manager.
-  ash::ash_client::Init();
-
-  catalog::mojom::CatalogPtr catalog;
-  service_binding_.GetConnector()->BindInterface(catalog::mojom::kServiceName,
-                                                 &catalog);
-
-  window_ = views::Widget::CreateWindowWithContextAndBounds(
-      new QuickLaunchUI(this, service_binding_.GetConnector(),
-                        std::move(catalog)),
-      nullptr, gfx::Rect(10, 640, 0, 0));
-  window_->GetNativeWindow()->GetHost()->window()->SetName("QuickLaunch");
-  window_->Show();
-}
-
-void QuickLaunchApplication::OnBindInterface(
-    const service_manager::BindSourceInfo& source_info,
-    const std::string& interface_name,
-    mojo::ScopedMessagePipeHandle interface_pipe) {
-  registry_.BindInterface(interface_name, std::move(interface_pipe));
-}
-
-}  // namespace quick_launch
diff --git a/ash/components/quick_launch/quick_launch_application.h b/ash/components/quick_launch/quick_launch_application.h
deleted file mode 100644
index d7a01c8..0000000
--- a/ash/components/quick_launch/quick_launch_application.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2016 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_COMPONENTS_QUICK_LAUNCH_QUICK_LAUNCH_APPLICATION_H_
-#define ASH_COMPONENTS_QUICK_LAUNCH_QUICK_LAUNCH_APPLICATION_H_
-
-#include <memory>
-
-#include "base/macros.h"
-#include "services/service_manager/public/cpp/binder_registry.h"
-#include "services/service_manager/public/cpp/service.h"
-#include "services/service_manager/public/cpp/service_binding.h"
-
-namespace views {
-class AuraInit;
-class Widget;
-}  // namespace views
-
-namespace quick_launch {
-
-class QuickLaunchApplication : public service_manager::Service {
- public:
-  explicit QuickLaunchApplication(
-      service_manager::mojom::ServiceRequest request);
-  ~QuickLaunchApplication() override;
-
-  void Quit();
-
-  void set_running_standalone(bool value) { running_standalone_ = value; }
-
- private:
-  // service_manager::Service:
-  void OnStart() override;
-  void OnBindInterface(const service_manager::BindSourceInfo& source_info,
-                       const std::string& interface_name,
-                       mojo::ScopedMessagePipeHandle interface_pipe) override;
-
-  service_manager::ServiceBinding service_binding_;
-
-  views::Widget* window_ = nullptr;
-
-  service_manager::BinderRegistry registry_;
-
-  std::unique_ptr<views::AuraInit> aura_init_;
-
-  bool running_standalone_ = false;
-
-  DISALLOW_COPY_AND_ASSIGN(QuickLaunchApplication);
-};
-
-}  // namespace quick_launch
-
-#endif  // ASH_COMPONENTS_QUICK_LAUNCH_QUICK_LAUNCH_APPLICATION_H_
diff --git a/ash/frame/non_client_frame_view_ash.cc b/ash/frame/non_client_frame_view_ash.cc
index af18ac2..5edf465 100644
--- a/ash/frame/non_client_frame_view_ash.cc
+++ b/ash/frame/non_client_frame_view_ash.cc
@@ -257,14 +257,15 @@
     immersive_helper_ =
         std::make_unique<NonClientFrameViewAshImmersiveHelper>(frame, this);
   }
-  Shell::Get()->AddShellObserver(this);
+  Shell::Get()->overview_controller()->AddObserver(this);
   Shell::Get()->split_view_controller()->AddObserver(this);
 
   frame_window->SetProperty(kNonClientFrameViewAshKey, this);
 }
 
 NonClientFrameViewAsh::~NonClientFrameViewAsh() {
-  Shell::Get()->RemoveShellObserver(this);
+  if (Shell::Get()->overview_controller())
+    Shell::Get()->overview_controller()->RemoveObserver(this);
   if (Shell::Get()->split_view_controller())
     Shell::Get()->split_view_controller()->RemoveObserver(this);
 }
diff --git a/ash/frame/non_client_frame_view_ash.h b/ash/frame/non_client_frame_view_ash.h
index f034ab4..2affcad 100644
--- a/ash/frame/non_client_frame_view_ash.h
+++ b/ash/frame/non_client_frame_view_ash.h
@@ -11,7 +11,7 @@
 #include "ash/frame/header_view.h"
 #include "ash/public/cpp/menu_utils.h"
 #include "ash/public/interfaces/menu.mojom.h"
-#include "ash/shell_observer.h"
+#include "ash/wm/overview/overview_observer.h"
 #include "ash/wm/splitview/split_view_controller.h"
 #include "base/macros.h"
 #include "base/optional.h"
@@ -40,7 +40,7 @@
 // the top of the screen. See also views::CustomFrameView and
 // BrowserNonClientFrameViewAsh.
 class ASH_EXPORT NonClientFrameViewAsh : public views::NonClientFrameView,
-                                         public ShellObserver,
+                                         public OverviewObserver,
                                          public SplitViewController::Observer,
                                          public views::ContextMenuController,
                                          public ui::SimpleMenuModel::Delegate {
@@ -116,7 +116,7 @@
   // header of v2 and ARC apps.
   virtual void SetShouldPaintHeader(bool paint);
 
-  // ShellObserver:
+  // OverviewObserver:
   void OnOverviewModeStarting() override;
   void OnOverviewModeEnded() override;
 
diff --git a/ash/frame/wide_frame_view.cc b/ash/frame/wide_frame_view.cc
index 5cc0b3f..b88e2ac 100644
--- a/ash/frame/wide_frame_view.cc
+++ b/ash/frame/wide_frame_view.cc
@@ -9,6 +9,8 @@
 #include "ash/public/cpp/caption_buttons/frame_caption_button_container_view.h"
 #include "ash/public/cpp/immersive/immersive_fullscreen_controller.h"
 #include "ash/public/cpp/window_properties.h"
+#include "ash/shell.h"
+#include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/wm_event.h"
 #include "base/metrics/user_metrics.h"
@@ -82,7 +84,7 @@
 
 WideFrameView::WideFrameView(views::Widget* target)
     : target_(target), widget_(std::make_unique<views::Widget>()) {
-  Shell::Get()->AddShellObserver(this);
+  Shell::Get()->overview_controller()->AddObserver(this);
   display::Screen::GetScreen()->AddObserver(this);
 
   aura::Window* target_window = target->GetNativeWindow();
@@ -110,7 +112,8 @@
 WideFrameView::~WideFrameView() {
   if (widget_)
     widget_->CloseNow();
-  Shell::Get()->RemoveShellObserver(this);
+  if (Shell::Get()->overview_controller())
+    Shell::Get()->overview_controller()->RemoveObserver(this);
   display::Screen::GetScreen()->RemoveObserver(this);
   if (target_) {
     GetTargetHeaderView()->SetShouldPaintHeader(true);
diff --git a/ash/frame/wide_frame_view.h b/ash/frame/wide_frame_view.h
index 8641292..ea97cdf 100644
--- a/ash/frame/wide_frame_view.h
+++ b/ash/frame/wide_frame_view.h
@@ -8,8 +8,7 @@
 #include "ash/ash_export.h"
 #include "ash/public/cpp/caption_buttons/caption_button_model.h"
 #include "ash/public/cpp/immersive/immersive_fullscreen_controller_delegate.h"
-#include "ash/shell.h"
-#include "ash/shell_observer.h"
+#include "ash/wm/overview/overview_observer.h"
 #include "ui/aura/window_observer.h"
 #include "ui/display/display_observer.h"
 #include "ui/views/widget/widget_delegate.h"
@@ -37,7 +36,7 @@
       public aura::WindowObserver,
       public display::DisplayObserver,
       public ash::ImmersiveFullscreenControllerDelegate,
-      public ash::ShellObserver {
+      public OverviewObserver {
  public:
   explicit WideFrameView(views::Widget* target);
   ~WideFrameView() override;
@@ -76,7 +75,7 @@
   void SetVisibleFraction(double visible_fraction) override;
   std::vector<gfx::Rect> GetVisibleBoundsInScreen() const override;
 
-  // ash::ShellObserver:
+  // OverviewObserver:
   void OnOverviewModeStarting() override;
   void OnOverviewModeEnded() override;
 
diff --git a/ash/login/login_screen_controller.cc b/ash/login/login_screen_controller.cc
index 6d97275..08dda98 100644
--- a/ash/login/login_screen_controller.cc
+++ b/ash/login/login_screen_controller.cc
@@ -563,6 +563,10 @@
   login_screen_client_->FocusOobeDialog();
 }
 
+void LoginScreenController::NotifyUserActivity() {
+  login_screen_client_->OnUserActivity();
+}
+
 void LoginScreenController::OnAuthenticateComplete(
     OnAuthenticateCallback callback,
     bool success) {
diff --git a/ash/login/login_screen_controller.h b/ash/login/login_screen_controller.h
index dacfefd..59962d3 100644
--- a/ash/login/login_screen_controller.h
+++ b/ash/login/login_screen_controller.h
@@ -91,6 +91,7 @@
   void ShowResetScreen();
   void ShowAccountAccessHelpApp();
   void FocusOobeDialog();
+  void NotifyUserActivity();
 
   // Add or remove an observer.
   void AddObserver(LoginScreenControllerObserver* observer);
diff --git a/ash/login/mock_login_screen_client.h b/ash/login/mock_login_screen_client.h
index 2c4281d..152ecfb 100644
--- a/ash/login/mock_login_screen_client.h
+++ b/ash/login/mock_login_screen_client.h
@@ -94,6 +94,7 @@
   MOCK_METHOD0(ShowAccountAccessHelpApp, void());
   MOCK_METHOD0(FocusOobeDialog, void());
   MOCK_METHOD1(OnFocusLeavingSystemTray, void(bool reverse));
+  MOCK_METHOD0(OnUserActivity, void());
 
  private:
   bool authenticate_user_callback_result_ = true;
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index b989925..270410f 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -44,6 +44,8 @@
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/ui_base_features.h"
+#include "ui/base/user_activity/user_activity_detector.h"
+#include "ui/base/user_activity/user_activity_observer.h"
 #include "ui/display/display.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/manager/managed_display_info.h"
@@ -276,6 +278,28 @@
 
 }  // namespace
 
+class LockContentsView::AutoLoginUserActivityHandler
+    : public ui::UserActivityObserver {
+ public:
+  AutoLoginUserActivityHandler() {
+    observer_.Add(ui::UserActivityDetector::Get());
+  }
+
+  ~AutoLoginUserActivityHandler() override = default;
+
+  void OnUserActivity(const ui::Event* event) override {
+    if (Shell::Get()->login_screen_controller()) {
+      Shell::Get()->login_screen_controller()->NotifyUserActivity();
+    }
+  }
+
+ private:
+  ScopedObserver<ui::UserActivityDetector, ui::UserActivityObserver> observer_{
+      this};
+
+  DISALLOW_COPY_AND_ASSIGN(AutoLoginUserActivityHandler);
+};
+
 LockContentsView::TestApi::TestApi(LockContentsView* view) : view_(view) {}
 
 LockContentsView::TestApi::~TestApi() = default;
@@ -354,6 +378,10 @@
       screen_type_(screen_type),
       data_dispatcher_(data_dispatcher),
       detachable_base_model_(std::move(detachable_base_model)) {
+  if (screen_type == LockScreen::ScreenType::kLogin)
+    auto_login_user_activity_handler_ =
+        std::make_unique<AutoLoginUserActivityHandler>();
+
   data_dispatcher_->AddObserver(this);
   display_observer_.Add(display::Screen::GetScreen());
   Shell::Get()->login_screen_controller()->AddObserver(this);
diff --git a/ash/login/ui/lock_contents_view.h b/ash/login/ui/lock_contents_view.h
index 5d0ca98..5eec2ad 100644
--- a/ash/login/ui/lock_contents_view.h
+++ b/ash/login/ui/lock_contents_view.h
@@ -223,6 +223,8 @@
     DISALLOW_COPY_AND_ASSIGN(UserState);
   };
 
+  class AutoLoginUserActivityHandler;
+
   using DisplayLayoutAction = base::RepeatingCallback<void(bool landscape)>;
 
   // Focus the next/previous widget.
@@ -406,6 +408,11 @@
   // Accelerators handled by login screen.
   std::map<ui::Accelerator, AcceleratorAction> accel_map_;
 
+  // Notifies Chrome when user activity is detected on the login screen so that
+  // the auto-login timer can be reset.
+  std::unique_ptr<AutoLoginUserActivityHandler>
+      auto_login_user_activity_handler_;
+
   DISALLOW_COPY_AND_ASSIGN(LockContentsView);
 };
 
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index 7e1e4288..f58395d 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -168,7 +168,6 @@
     "//ash/public/interfaces:interfaces_internal",
     "//base",
     "//chromeos/services/multidevice_setup/public/mojom",
-    "//services/catalog/public/mojom",
     "//services/content/public/mojom",
     "//services/data_decoder/public/mojom",
     "//services/device/public/mojom",
diff --git a/ash/public/cpp/DEPS b/ash/public/cpp/DEPS
index 5465f0b..b969121 100644
--- a/ash/public/cpp/DEPS
+++ b/ash/public/cpp/DEPS
@@ -1,6 +1,5 @@
 include_rules = [
   "+components/prefs",
-  "+services/catalog/public",
   "+services/data_decoder/public",
   "+services/device/public",
   "+skia/public/interfaces",
diff --git a/ash/public/cpp/accelerators.h b/ash/public/cpp/accelerators.h
index 29f6796..ccb1287 100644
--- a/ash/public/cpp/accelerators.h
+++ b/ash/public/cpp/accelerators.h
@@ -20,20 +20,6 @@
   BRIGHTNESS_UP,
   CYCLE_BACKWARD_MRU,
   CYCLE_FORWARD_MRU,
-  DEBUG_PRINT_LAYER_HIERARCHY,
-  DEBUG_PRINT_VIEW_HIERARCHY,
-  DEBUG_PRINT_WINDOW_HIERARCHY,
-  DEBUG_SHOW_QUICK_LAUNCH,
-  DEBUG_SHOW_TOAST,
-  DEBUG_TOGGLE_DEVICE_SCALE_FACTOR,
-  DEBUG_TOGGLE_SHOW_DEBUG_BORDERS,
-  DEBUG_TOGGLE_SHOW_FPS_COUNTER,
-  DEBUG_TOGGLE_SHOW_PAINT_RECTS,
-  DEBUG_TOGGLE_TOUCH_PAD,
-  DEBUG_TOGGLE_TOUCH_SCREEN,
-  DEBUG_TOGGLE_TABLET_MODE,
-  DEBUG_TOGGLE_WALLPAPER_MODE,
-  DEBUG_TRIGGER_CRASH,  // Intentionally crash the ash process.
   DEV_ADD_REMOVE_DISPLAY,
   DEV_TOGGLE_UNIFIED_DESKTOP,
   DISABLE_CAPS_LOCK,
@@ -117,6 +103,22 @@
   WINDOW_CYCLE_SNAP_RIGHT,
   WINDOW_MINIMIZE,
   WINDOW_POSITION_CENTER,
+
+  // Debug accelerators are intentionally at the end, so that if you remove one
+  // you don't need to update tests which check hashes of the ids.
+  DEBUG_PRINT_LAYER_HIERARCHY,
+  DEBUG_PRINT_VIEW_HIERARCHY,
+  DEBUG_PRINT_WINDOW_HIERARCHY,
+  DEBUG_SHOW_TOAST,
+  DEBUG_TOGGLE_DEVICE_SCALE_FACTOR,
+  DEBUG_TOGGLE_SHOW_DEBUG_BORDERS,
+  DEBUG_TOGGLE_SHOW_FPS_COUNTER,
+  DEBUG_TOGGLE_SHOW_PAINT_RECTS,
+  DEBUG_TOGGLE_TOUCH_PAD,
+  DEBUG_TOGGLE_TOUCH_SCREEN,
+  DEBUG_TOGGLE_TABLET_MODE,
+  DEBUG_TOGGLE_WALLPAPER_MODE,
+  DEBUG_TRIGGER_CRASH,  // Intentionally crash the ash process.
 };
 
 struct AcceleratorData {
diff --git a/ash/public/cpp/manifest.cc b/ash/public/cpp/manifest.cc
index b738445..e4ad061 100644
--- a/ash/public/cpp/manifest.cc
+++ b/ash/public/cpp/manifest.cc
@@ -43,7 +43,6 @@
 #include "ash/public/interfaces/wallpaper.mojom.h"
 #include "base/no_destructor.h"
 #include "chromeos/services/multidevice_setup/public/mojom/constants.mojom.h"
-#include "services/catalog/public/mojom/constants.mojom.h"
 #include "services/content/public/mojom/constants.mojom.h"
 #include "services/data_decoder/public/mojom/constants.mojom.h"
 #include "services/device/public/mojom/constants.mojom.h"
@@ -114,7 +113,6 @@
           .RequireCapability(mojom::kPrefConnectorServiceName, "pref_connector")
           .RequireCapability(viz::mojom::kVizServiceName, "ozone")
           .RequireCapability(viz::mojom::kVizServiceName, "viz_host")
-          .RequireCapability(catalog::mojom::kServiceName, "directory")
           .RequireCapability(ws::mojom::kServiceName, "ozone")
           .RequireCapability(ws::mojom::kServiceName, "window_manager")
           .RequireCapability(device::mojom::kServiceName,
diff --git a/ash/public/interfaces/login_screen.mojom b/ash/public/interfaces/login_screen.mojom
index eaab8cf..b0ad7fb 100644
--- a/ash/public/interfaces/login_screen.mojom
+++ b/ash/public/interfaces/login_screen.mojom
@@ -346,4 +346,7 @@
   // the login screen / OOBE. |reverse| is true when the focus moves in the
   // reversed direction.
   OnFocusLeavingSystemTray(bool reverse);
+
+  // Used by Ash to signal that user activity occurred on the login screen.
+  OnUserActivity();
 };
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index 5a59daf..b6d66c8 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -179,6 +179,7 @@
   DCHECK(shelf_widget_);
   DCHECK(shelf_);
   Shell::Get()->AddShellObserver(this);
+  Shell::Get()->overview_controller()->AddObserver(this);
   Shell::Get()->app_list_controller()->AddObserver(this);
   Shell::Get()->lock_state_controller()->AddObserver(this);
   Shell::Get()->activation_client()->AddObserver(this);
@@ -204,6 +205,8 @@
   // not exist.
   if (Shell::Get()->app_list_controller())
     Shell::Get()->app_list_controller()->RemoveObserver(this);
+  if (Shell::Get()->overview_controller())
+    Shell::Get()->overview_controller()->RemoveObserver(this);
 }
 
 void ShelfLayoutManager::PrepareForShutdown() {
@@ -549,6 +552,14 @@
   UpdateVisibilityState();
 }
 
+void ShelfLayoutManager::OnSplitViewModeStarted() {
+  MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
+}
+
+void ShelfLayoutManager::OnSplitViewModeEnded() {
+  MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
+}
+
 void ShelfLayoutManager::OnOverviewModeStartingAnimationComplete(
     bool canceled) {
   UpdateVisibilityState();
@@ -560,14 +571,6 @@
   MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
 }
 
-void ShelfLayoutManager::OnSplitViewModeStarted() {
-  MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
-}
-
-void ShelfLayoutManager::OnSplitViewModeEnded() {
-  MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
-}
-
 void ShelfLayoutManager::OnAppListVisibilityChanged(bool shown,
                                                     int64_t display_id) {
   // Shell may be under destruction.
diff --git a/ash/shelf/shelf_layout_manager.h b/ash/shelf/shelf_layout_manager.h
index 395edfb..a62bc36 100644
--- a/ash/shelf/shelf_layout_manager.h
+++ b/ash/shelf/shelf_layout_manager.h
@@ -16,6 +16,7 @@
 #include "ash/system/locale/locale_update_controller.h"
 #include "ash/wallpaper/wallpaper_controller_observer.h"
 #include "ash/wm/lock_state_observer.h"
+#include "ash/wm/overview/overview_observer.h"
 #include "ash/wm/wm_snap_to_pixel_layout_manager.h"
 #include "ash/wm/workspace/workspace_types.h"
 #include "base/macros.h"
@@ -52,6 +53,7 @@
 class ASH_EXPORT ShelfLayoutManager
     : public AppListControllerObserver,
       public ShellObserver,
+      public OverviewObserver,
       public ::wm::ActivationChangeObserver,
       public keyboard::KeyboardControllerObserver,
       public LockStateObserver,
@@ -155,11 +157,13 @@
   // ShellObserver:
   void OnShelfAutoHideBehaviorChanged(aura::Window* root_window) override;
   void OnPinnedStateChanged(aura::Window* pinned_window) override;
-  void OnOverviewModeStartingAnimationComplete(bool canceled) override;
-  void OnOverviewModeEndingAnimationComplete(bool canceled) override;
   void OnSplitViewModeStarted() override;
   void OnSplitViewModeEnded() override;
 
+  // OverviewObserver:
+  void OnOverviewModeStartingAnimationComplete(bool canceled) override;
+  void OnOverviewModeEndingAnimationComplete(bool canceled) override;
+
   // AppListControllerObserver:
   void OnAppListVisibilityChanged(bool shown, int64_t display_id) override;
   void OnHomeLauncherTargetPositionChanged(bool showing,
diff --git a/ash/shell.cc b/ash/shell.cc
index 93168b7..693a8736 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -575,32 +575,6 @@
     root_window_controller->UpdateAfterLoginStatusChange(status);
 }
 
-void Shell::NotifyOverviewModeStarting() {
-  for (auto& observer : shell_observers_)
-    observer.OnOverviewModeStarting();
-}
-
-void Shell::NotifyOverviewModeStartingAnimationComplete(bool canceled) {
-  for (auto& observer : shell_observers_)
-    observer.OnOverviewModeStartingAnimationComplete(canceled);
-}
-
-void Shell::NotifyOverviewModeEnding(OverviewSession* overview_session) {
-  DCHECK(overview_session);
-  for (auto& observer : shell_observers_)
-    observer.OnOverviewModeEnding(overview_session);
-}
-
-void Shell::NotifyOverviewModeEnded() {
-  for (auto& observer : shell_observers_)
-    observer.OnOverviewModeEnded();
-}
-
-void Shell::NotifyOverviewModeEndingAnimationComplete(bool canceled) {
-  for (auto& observer : shell_observers_)
-    observer.OnOverviewModeEndingAnimationComplete(canceled);
-}
-
 void Shell::NotifySplitViewModeStarting() {
   for (auto& observer : shell_observers_)
     observer.OnSplitViewModeStarting();
diff --git a/ash/shell.h b/ash/shell.h
index 52f5027..53ab4a3 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -199,7 +199,6 @@
 class WindowServiceOwner;
 class WindowCycleController;
 class WindowPositioner;
-class OverviewSession;
 class OverviewController;
 class WindowTreeHostManager;
 
@@ -600,23 +599,6 @@
   // TODO(oshima): Investigate if we can merge this and |OnLoginStateChanged|.
   void UpdateAfterLoginStatusChange(LoginStatus status);
 
-  // Notifies observers that overview mode is about to be started (before the
-  // windows get re-arranged).
-  void NotifyOverviewModeStarting();
-
-  // Notifies observers that the start overview mode animation has completed.
-  void NotifyOverviewModeStartingAnimationComplete(bool canceled);
-
-  // Notifies observers that overview mode is about to end (before the windows
-  // restore themselves). |overview_session| must not be null.
-  void NotifyOverviewModeEnding(OverviewSession* overview_session);
-
-  // Notifies observers that overview mode has ended.
-  void NotifyOverviewModeEnded();
-
-  // Notifies observers that the end overview mode animation has completed.
-  void NotifyOverviewModeEndingAnimationComplete(bool canceled);
-
   // Notifies observers that split view mode is about to be started (before the
   // window gets snapped and activated).
   void NotifySplitViewModeStarting();
diff --git a/ash/shell/content/client/DEPS b/ash/shell/content/client/DEPS
index b4e4b71..3f47124 100644
--- a/ash/shell/content/client/DEPS
+++ b/ash/shell/content/client/DEPS
@@ -1,5 +1,4 @@
 include_rules = [
-  "+ash/components/quick_launch",
   "+ash/components/shortcut_viewer",
   "+ash/components/tap_visualizer",
   "+components/discardable_memory/public/interfaces",
diff --git a/ash/shell/content/client/shell_browser_main_parts.cc b/ash/shell/content/client/shell_browser_main_parts.cc
index ba370fc..fb46fed 100644
--- a/ash/shell/content/client/shell_browser_main_parts.cc
+++ b/ash/shell/content/client/shell_browser_main_parts.cc
@@ -7,7 +7,6 @@
 #include <memory>
 #include <utility>
 
-#include "ash/components/quick_launch/public/mojom/constants.mojom.h"
 #include "ash/components/shortcut_viewer/public/mojom/shortcut_viewer.mojom.h"
 #include "ash/components/tap_visualizer/public/mojom/tap_visualizer.mojom.h"
 #include "ash/keyboard/test_keyboard_ui.h"
@@ -156,8 +155,6 @@
   connector->WarmService(service_manager::ServiceFilter::ByName(
       test_ime_driver::mojom::kServiceName));
   connector->WarmService(service_manager::ServiceFilter::ByName(
-      quick_launch::mojom::kServiceName));
-  connector->WarmService(service_manager::ServiceFilter::ByName(
       tap_visualizer::mojom::kServiceName));
   shortcut_viewer::mojom::ShortcutViewerPtr shortcut_viewer;
   connector->BindInterface(service_manager::ServiceFilter::ByName(
diff --git a/ash/shell/content/client/shell_content_browser_client.cc b/ash/shell/content/client/shell_content_browser_client.cc
index 1558636..4d6e587 100644
--- a/ash/shell/content/client/shell_content_browser_client.cc
+++ b/ash/shell/content/client/shell_content_browser_client.cc
@@ -7,8 +7,6 @@
 #include <utility>
 
 #include "ash/ash_service.h"
-#include "ash/components/quick_launch/public/cpp/manifest.h"
-#include "ash/components/quick_launch/public/mojom/constants.mojom.h"
 #include "ash/components/shortcut_viewer/public/cpp/manifest.h"
 #include "ash/components/shortcut_viewer/public/mojom/shortcut_viewer.mojom.h"
 #include "ash/components/tap_visualizer/public/cpp/manifest.h"
@@ -62,7 +60,6 @@
       service_manager::ManifestBuilder()
           .PackageService(service_manager::Manifest(ash::GetManifest())
                               .Amend(ash::GetManifestOverlayForTesting()))
-          .PackageService(quick_launch::GetManifest())
           .PackageService(shortcut_viewer::GetManifest())
           .PackageService(tap_visualizer::GetManifest())
           .PackageService(test_ime_driver::GetManifest())
@@ -106,8 +103,6 @@
 
 void ShellContentBrowserClient::RegisterOutOfProcessServices(
     OutOfProcessServiceMap* services) {
-  (*services)[quick_launch::mojom::kServiceName] = base::BindRepeating(
-      &base::ASCIIToUTF16, quick_launch::mojom::kServiceName);
   (*services)[shortcut_viewer::mojom::kServiceName] = base::BindRepeating(
       &base::ASCIIToUTF16, shortcut_viewer::mojom::kServiceName);
   (*services)[tap_visualizer::mojom::kServiceName] = base::BindRepeating(
diff --git a/ash/shell/content/client/shell_main_delegate.cc b/ash/shell/content/client/shell_main_delegate.cc
index 2759fcb..cd385e5 100644
--- a/ash/shell/content/client/shell_main_delegate.cc
+++ b/ash/shell/content/client/shell_main_delegate.cc
@@ -4,8 +4,6 @@
 
 #include "ash/shell/content/client/shell_main_delegate.h"
 
-#include "ash/components/quick_launch/public/mojom/constants.mojom.h"
-#include "ash/components/quick_launch/quick_launch_application.h"
 #include "ash/components/shortcut_viewer/public/mojom/shortcut_viewer.mojom.h"
 #include "ash/components/shortcut_viewer/shortcut_viewer_application.h"
 #include "ash/components/tap_visualizer/public/mojom/tap_visualizer.mojom.h"
@@ -32,13 +30,6 @@
   content::UtilityThread::Get()->ReleaseProcess();
 }
 
-std::unique_ptr<service_manager::Service> CreateQuickLaunch(
-    service_manager::mojom::ServiceRequest request) {
-  logging::SetLogPrefix("quick");
-  return std::make_unique<quick_launch::QuickLaunchApplication>(
-      std::move(request));
-}
-
 std::unique_ptr<service_manager::Service> CreateShortcutViewer(
     service_manager::mojom::ServiceRequest request) {
   logging::SetLogPrefix("shortcut");
@@ -67,9 +58,7 @@
       const std::string& service_name,
       service_manager::mojom::ServiceRequest request) override {
     std::unique_ptr<service_manager::Service> service;
-    if (service_name == quick_launch::mojom::kServiceName)
-      service = CreateQuickLaunch(std::move(request));
-    else if (service_name == test_ime_driver::mojom::kServiceName)
+    if (service_name == test_ime_driver::mojom::kServiceName)
       service = CreateTestImeDriver(std::move(request));
     else if (service_name == shortcut_viewer::mojom::kServiceName)
       service = CreateShortcutViewer(std::move(request));
diff --git a/ash/shell_observer.h b/ash/shell_observer.h
index ed518fb..11d56cb 100644
--- a/ash/shell_observer.h
+++ b/ash/shell_observer.h
@@ -16,8 +16,6 @@
 
 namespace ash {
 
-class OverviewSession;
-
 class ASH_EXPORT ShellObserver {
  public:
 
@@ -40,27 +38,6 @@
   // Invoked when |pinned_window| enter or exit pinned mode.
   virtual void OnPinnedStateChanged(aura::Window* pinned_window) {}
 
-  // Called when the overview mode is about to be started (before the windows
-  // get re-arranged).
-  virtual void OnOverviewModeStarting() {}
-
-  // Called after the animations that happen when overview mode is started are
-  // complete. If |canceled| it means overview was quit before the start
-  // animations were finished.
-  virtual void OnOverviewModeStartingAnimationComplete(bool canceled) {}
-
-  // Called when the overview mode is about to end (bofore the windows restore
-  // themselves). |overview_session| will not be null.
-  virtual void OnOverviewModeEnding(OverviewSession* overview_session) {}
-
-  // Called after overview mode has ended.
-  virtual void OnOverviewModeEnded() {}
-
-  // Called after the animations that happen when overview mode is ended are
-  // complete. If |canceled| it means overview was reentered before the exit
-  // animations were finished.
-  virtual void OnOverviewModeEndingAnimationComplete(bool canceled) {}
-
   // Called when the split view mode is about to be started before the window
   // gets snapped and activated).
   virtual void OnSplitViewModeStarting() {}
diff --git a/ash/system/flag_warning/flag_warning_tray.cc b/ash/system/flag_warning/flag_warning_tray.cc
index 85de661..16a7c2a3 100644
--- a/ash/system/flag_warning/flag_warning_tray.cc
+++ b/ash/system/flag_warning/flag_warning_tray.cc
@@ -6,7 +6,7 @@
 
 #include <memory>
 
-#include "ash/components/quick_launch/public/mojom/constants.mojom.h"
+#include "ash/components/shortcut_viewer/public/mojom/shortcut_viewer.mojom.h"
 #include "ash/public/cpp/ash_typography.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shelf/shelf.h"
@@ -58,11 +58,11 @@
                                     const ui::Event& event) {
   DCHECK_EQ(button_, sender);
 
-  // Open the quick launch mojo mini-app to demonstrate that mini-apps work.
-  //
-  // TODO(https://crbug.com/904148): This should not use |WarmService()|.
-  Shell::Get()->connector()->WarmService(service_manager::ServiceFilter::ByName(
-      quick_launch::mojom::kServiceName));
+  // Open the shortcut viewer mini-app to demonstrate that mini-apps work.
+  shortcut_viewer::mojom::ShortcutViewerPtr shortcut_viewer_ptr;
+  Shell::Get()->connector()->BindInterface(shortcut_viewer::mojom::kServiceName,
+                                           &shortcut_viewer_ptr);
+  shortcut_viewer_ptr->Toggle(base::TimeTicks::Now());
 }
 
 void FlagWarningTray::GetAccessibleNodeData(ui::AXNodeData* node_data) {
diff --git a/ash/system/overview/overview_button_tray.cc b/ash/system/overview/overview_button_tray.cc
index e5056f3..865c765 100644
--- a/ash/system/overview/overview_button_tray.cc
+++ b/ash/system/overview/overview_button_tray.cc
@@ -50,14 +50,15 @@
   // horizontal shelf, no separator is required.
   set_separator_visibility(false);
 
-  Shell::Get()->AddShellObserver(this);
+  Shell::Get()->overview_controller()->AddObserver(this);
   Shell::Get()->tablet_mode_controller()->AddObserver(this);
 }
 
 OverviewButtonTray::~OverviewButtonTray() {
+  if (Shell::Get()->overview_controller())
+    Shell::Get()->overview_controller()->RemoveObserver(this);
   if (Shell::Get()->tablet_mode_controller())
     Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
-  Shell::Get()->RemoveShellObserver(this);
 }
 
 void OverviewButtonTray::UpdateAfterLoginStatusChange(LoginStatus status) {
diff --git a/ash/system/overview/overview_button_tray.h b/ash/system/overview/overview_button_tray.h
index 0d16b4b..bf6419e 100644
--- a/ash/system/overview/overview_button_tray.h
+++ b/ash/system/overview/overview_button_tray.h
@@ -7,8 +7,8 @@
 
 #include "ash/ash_export.h"
 #include "ash/session/session_observer.h"
-#include "ash/shell_observer.h"
 #include "ash/system/tray/tray_background_view.h"
+#include "ash/wm/overview/overview_observer.h"
 #include "ash/wm/tablet_mode/tablet_mode_observer.h"
 #include "base/macros.h"
 #include "ui/events/event_constants.h"
@@ -26,7 +26,7 @@
 // provide any bubble view windows.
 class ASH_EXPORT OverviewButtonTray : public TrayBackgroundView,
                                       public SessionObserver,
-                                      public ShellObserver,
+                                      public OverviewObserver,
                                       public TabletModeObserver {
  public:
   // Second taps within this time will be counted as double taps. Use this
@@ -57,7 +57,7 @@
   // SessionObserver:
   void OnSessionStateChanged(session_manager::SessionState state) override;
 
-  // ShellObserver:
+  // OverviewObserver:
   void OnOverviewModeStarting() override;
   void OnOverviewModeEnded() override;
 
diff --git a/ash/system/power/battery_notification.cc b/ash/system/power/battery_notification.cc
index 1791080..174678a 100644
--- a/ash/system/power/battery_notification.cc
+++ b/ash/system/power/battery_notification.cc
@@ -69,25 +69,25 @@
       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BATTERY_PERCENT),
       static_cast<double>(status.GetRoundedBatteryPercent()) / 100.0);
 
-  const base::TimeDelta time = status.IsBatteryCharging()
-                                   ? status.GetBatteryTimeToFull()
-                                   : status.GetBatteryTimeToEmpty();
+  const base::Optional<base::TimeDelta> time =
+      status.IsBatteryCharging() ? status.GetBatteryTimeToFull()
+                                 : status.GetBatteryTimeToEmpty();
   base::string16 time_message;
   if (status.IsUsbChargerConnected()) {
     time_message = l10n_util::GetStringUTF16(
         IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE);
-  } else if (power_utils::ShouldDisplayBatteryTime(time) &&
+  } else if (time && power_utils::ShouldDisplayBatteryTime(*time) &&
              !status.IsBatteryDischargingOnLinePower()) {
     if (status.IsBatteryCharging()) {
       base::string16 duration;
-      if (!TimeDurationFormat(time, base::DURATION_WIDTH_NARROW, &duration))
-        LOG(ERROR) << "Failed to format duration " << time;
+      if (!TimeDurationFormat(*time, base::DURATION_WIDTH_NARROW, &duration))
+        LOG(ERROR) << "Failed to format duration " << *time;
       time_message = l10n_util::GetStringFUTF16(
           IDS_ASH_STATUS_TRAY_BATTERY_TIME_UNTIL_FULL, duration);
     } else {
       // This is a low battery warning prompting the user in minutes.
       time_message = ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING,
-                                            ui::TimeFormat::LENGTH_LONG, time);
+                                            ui::TimeFormat::LENGTH_LONG, *time);
     }
   }
 
diff --git a/ash/system/power/power_notification_controller.cc b/ash/system/power/power_notification_controller.cc
index 596ab05..0146875 100644
--- a/ash/system/power/power_notification_controller.cc
+++ b/ash/system/power/power_notification_controller.cc
@@ -185,10 +185,20 @@
 }
 
 bool PowerNotificationController::UpdateNotificationStateForRemainingTime() {
+  const base::Optional<base::TimeDelta> remaining_time =
+      PowerStatus::Get()->GetBatteryTimeToEmpty();
+
+  // Check that powerd actually provided an estimate. It doesn't if the battery
+  // current is so close to zero that the estimate would be huge.
+  if (!remaining_time) {
+    notification_state_ = NOTIFICATION_NONE;
+    return false;
+  }
+
   // The notification includes a rounded minutes value, so round the estimate
   // received from the power manager to match.
-  const int remaining_minutes = static_cast<int>(
-      PowerStatus::Get()->GetBatteryTimeToEmpty().InSecondsF() / 60.0 + 0.5);
+  const int remaining_minutes =
+      static_cast<int>(remaining_time->InSecondsF() / 60.0 + 0.5);
 
   if (remaining_minutes >= kNoWarningMinutes ||
       PowerStatus::Get()->IsBatteryFull()) {
diff --git a/ash/system/power/power_notification_controller_unittest.cc b/ash/system/power/power_notification_controller_unittest.cc
index d33adc7f..c72e219 100644
--- a/ash/system/power/power_notification_controller_unittest.cc
+++ b/ash/system/power/power_notification_controller_unittest.cc
@@ -494,4 +494,13 @@
   }
 }
 
+// Test that a notification isn't shown if powerd sends a -1 time-to-empty value
+// to indicate that it couldn't produce an estimate: https://crbug.com/930358
+TEST_F(PowerNotificationControllerTest, IgnoreMissingBatteryEstimates) {
+  PowerSupplyProperties proto = DefaultPowerSupplyProperties();
+  proto.set_battery_time_to_empty_sec(-1);
+  UpdateNotificationState(proto, PowerNotificationController::NOTIFICATION_NONE,
+                          false, false);
+}
+
 }  // namespace ash
diff --git a/ash/system/power/power_status.cc b/ash/system/power/power_status.cc
index 47a1dcd..b3ed193 100644
--- a/ash/system/power/power_status.cc
+++ b/ash/system/power/power_status.cc
@@ -278,11 +278,23 @@
   return proto_.is_calculating_battery_time();
 }
 
-base::TimeDelta PowerStatus::GetBatteryTimeToEmpty() const {
+base::Optional<base::TimeDelta> PowerStatus::GetBatteryTimeToEmpty() const {
+  // powerd omits the field if no battery is present and sends -1 if it couldn't
+  // compute a reasonable estimate.
+  if (!proto_.has_battery_time_to_empty_sec() ||
+      proto_.battery_time_to_empty_sec() < 0) {
+    return base::nullopt;
+  }
   return base::TimeDelta::FromSeconds(proto_.battery_time_to_empty_sec());
 }
 
-base::TimeDelta PowerStatus::GetBatteryTimeToFull() const {
+base::Optional<base::TimeDelta> PowerStatus::GetBatteryTimeToFull() const {
+  // powerd omits the field if no battery is present and sends -1 if it couldn't
+  // compute a reasonable estimate.
+  if (!proto_.has_battery_time_to_full_sec() ||
+      proto_.battery_time_to_full_sec() < 0) {
+    return base::nullopt;
+  }
   return base::TimeDelta::FromSeconds(proto_.battery_time_to_full_sec());
 }
 
@@ -389,7 +401,7 @@
     return battery_percentage_accessible;
 
   base::string16 battery_time_accessible = base::string16();
-  const base::TimeDelta time =
+  const base::Optional<base::TimeDelta> time =
       IsBatteryCharging() ? GetBatteryTimeToFull() : GetBatteryTimeToEmpty();
 
   if (IsUsbChargerConnected()) {
@@ -398,10 +410,10 @@
   } else if (IsBatteryTimeBeingCalculated()) {
     battery_time_accessible = l10n_util::GetStringUTF16(
         IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING_ACCESSIBLE);
-  } else if (power_utils::ShouldDisplayBatteryTime(time) &&
+  } else if (time && power_utils::ShouldDisplayBatteryTime(*time) &&
              !IsBatteryDischargingOnLinePower()) {
     int hour = 0, min = 0;
-    power_utils::SplitTimeIntoHoursAndMinutes(time, &hour, &min);
+    power_utils::SplitTimeIntoHoursAndMinutes(*time, &hour, &min);
     base::string16 minute =
         min < 10 ? base::ASCIIToUTF16("0") + base::NumberToString16(min)
                  : base::NumberToString16(min);
@@ -432,14 +444,15 @@
       status =
           l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING);
     } else {
-      base::TimeDelta time = IsBatteryCharging() ? GetBatteryTimeToFull()
+      base::Optional<base::TimeDelta> time = IsBatteryCharging()
+                                                 ? GetBatteryTimeToFull()
                                                  : GetBatteryTimeToEmpty();
-      if (power_utils::ShouldDisplayBatteryTime(time) &&
+      if (time && power_utils::ShouldDisplayBatteryTime(*time) &&
           !IsBatteryDischargingOnLinePower()) {
         base::string16 duration;
-        if (!base::TimeDurationFormat(time, base::DURATION_WIDTH_NUMERIC,
+        if (!base::TimeDurationFormat(*time, base::DURATION_WIDTH_NUMERIC,
                                       &duration))
-          LOG(ERROR) << "Failed to format duration " << time;
+          LOG(ERROR) << "Failed to format duration " << *time;
         status = l10n_util::GetStringFUTF16(
             IsBatteryCharging()
                 ? IDS_ASH_STATUS_TRAY_BATTERY_TIME_UNTIL_FULL_SHORT
diff --git a/ash/system/power/power_status.h b/ash/system/power/power_status.h
index d46bbbf..1e63fb3 100644
--- a/ash/system/power/power_status.h
+++ b/ash/system/power/power_status.h
@@ -11,6 +11,7 @@
 #include "ash/ash_export.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
+#include "base/optional.h"
 #include "base/strings/string16.h"
 #include "base/time/time.h"
 #include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
@@ -146,8 +147,12 @@
   // Returns the estimated time until the battery is empty (if line power
   // is disconnected) or full (if line power is connected). These estimates
   // should only be used if IsBatteryTimeBeingCalculated() returns false.
-  base::TimeDelta GetBatteryTimeToEmpty() const;
-  base::TimeDelta GetBatteryTimeToFull() const;
+  //
+  // Irrespective of IsBatteryTimeBeingCalculated(), estimates may be
+  // unavailable if powerd didn't provide them because the battery current was
+  // close to zero (resulting in time estimates approaching infinity).
+  base::Optional<base::TimeDelta> GetBatteryTimeToEmpty() const;
+  base::Optional<base::TimeDelta> GetBatteryTimeToFull() const;
 
   // Returns true if line power (including a charger of any type) is connected.
   bool IsLinePowerConnected() const;
diff --git a/ash/system/power/power_status_unittest.cc b/ash/system/power/power_status_unittest.cc
index 5e00008..3a3e5c5 100644
--- a/ash/system/power/power_status_unittest.cc
+++ b/ash/system/power/power_status_unittest.cc
@@ -236,4 +236,59 @@
   EXPECT_FALSE(gfx::test::AreImagesEqual(image_99, image_100));
 }
 
+// Tests that positive time-to-full and time-to-empty estimates are honored.
+TEST_F(PowerStatusTest, PositiveBatteryTimeEstimates) {
+  constexpr auto kTime = base::TimeDelta::FromSeconds(120);
+
+  PowerSupplyProperties prop;
+  prop.set_external_power(PowerSupplyProperties::AC);
+  prop.set_battery_state(PowerSupplyProperties::CHARGING);
+  prop.set_battery_time_to_full_sec(kTime.InSeconds());
+  power_status_->SetProtoForTesting(prop);
+  base::Optional<base::TimeDelta> time = power_status_->GetBatteryTimeToFull();
+  ASSERT_TRUE(time);
+  EXPECT_EQ(kTime, *time);
+
+  prop.Clear();
+  prop.set_external_power(PowerSupplyProperties::DISCONNECTED);
+  prop.set_battery_state(PowerSupplyProperties::DISCHARGING);
+  prop.set_battery_time_to_empty_sec(kTime.InSeconds());
+  power_status_->SetProtoForTesting(prop);
+  time = power_status_->GetBatteryTimeToEmpty();
+  ASSERT_TRUE(time);
+  EXPECT_EQ(kTime, *time);
+}
+
+// Tests that missing time-to-full and time-to-empty estimates (which powerd
+// sends when no battery is present) and negative ones (which powerd sends when
+// the battery current is close to zero) are disregarded:
+// https://crbug.com/930358
+TEST_F(PowerStatusTest, MissingBatteryTimeEstimates) {
+  // No battery.
+  PowerSupplyProperties prop;
+  prop.set_external_power(PowerSupplyProperties::AC);
+  prop.set_battery_state(PowerSupplyProperties::NOT_PRESENT);
+  power_status_->SetProtoForTesting(prop);
+  base::Optional<base::TimeDelta> time = power_status_->GetBatteryTimeToFull();
+  EXPECT_FALSE(time) << *time << " returned despite missing battery";
+  time = power_status_->GetBatteryTimeToEmpty();
+  EXPECT_FALSE(time) << *time << " returned despite missing battery";
+
+  // Battery is charging, but negative estimate provided.
+  prop.set_battery_state(PowerSupplyProperties::CHARGING);
+  prop.set_battery_time_to_full_sec(-1);
+  power_status_->SetProtoForTesting(prop);
+  time = power_status_->GetBatteryTimeToFull();
+  EXPECT_FALSE(time) << *time << " returned despite negative estimate";
+
+  // Battery is discharging, but negative estimate provided.
+  prop.Clear();
+  prop.set_external_power(PowerSupplyProperties::DISCONNECTED);
+  prop.set_battery_state(PowerSupplyProperties::DISCHARGING);
+  prop.set_battery_time_to_empty_sec(-1);
+  power_status_->SetProtoForTesting(prop);
+  time = power_status_->GetBatteryTimeToEmpty();
+  EXPECT_FALSE(time) << *time << " returned despite negative estimate";
+}
+
 }  // namespace ash
diff --git a/ash/wm/overview/overview_controller.cc b/ash/wm/overview/overview_controller.cc
index 0e74d70..ad1bb4c 100644
--- a/ash/wm/overview/overview_controller.cc
+++ b/ash/wm/overview/overview_controller.cc
@@ -360,7 +360,8 @@
 
     overview_session_ = std::make_unique<OverviewSession>(this);
     overview_session_->set_enter_exit_overview_type(new_type);
-    Shell::Get()->NotifyOverviewModeStarting();
+    for (auto& observer : observers_)
+      observer.OnOverviewModeStarting();
     overview_session_->Init(windows, hide_windows);
     if (IsBlurAllowed())
       overview_blur_controller_->Blur(/*animate_only=*/false);
@@ -375,7 +376,8 @@
   if (IsBlurAllowed())
     overview_blur_controller_->Blur(/*animate_only=*/true);
 
-  Shell::Get()->NotifyOverviewModeStartingAnimationComplete(canceled);
+  for (auto& observer : observers_)
+    observer.OnOverviewModeStartingAnimationComplete(canceled);
   if (overview_session_)
     overview_session_->OnStartingAnimationComplete(canceled);
   UnpauseOcclusionTracker(kOcclusionPauseDurationForStartMs);
@@ -388,7 +390,8 @@
   if (IsBlurAllowed() && !canceled)
     overview_blur_controller_->Unblur();
 
-  Shell::Get()->NotifyOverviewModeEndingAnimationComplete(canceled);
+  for (auto& observer : observers_)
+    observer.OnOverviewModeEndingAnimationComplete(canceled);
   UnpauseOcclusionTracker(occlusion_pause_duration_for_end_ms_);
 }
 
@@ -534,15 +537,12 @@
       base::TimeDelta::FromMilliseconds(delay));
 }
 
-std::vector<aura::Window*>
-OverviewController::GetWindowsListInOverviewGridsForTesting() {
-  std::vector<aura::Window*> windows;
-  for (const std::unique_ptr<OverviewGrid>& grid :
-       overview_session_->grid_list_for_testing()) {
-    for (const auto& overview_session_item : grid->window_list())
-      windows.push_back(overview_session_item->GetWindow());
-  }
-  return windows;
+void OverviewController::AddObserver(OverviewObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void OverviewController::RemoveObserver(OverviewObserver* observer) {
+  observers_.RemoveObserver(observer);
 }
 
 void OverviewController::DelayedUpdateMaskAndShadow() {
@@ -570,12 +570,14 @@
   // Do not show mask and show during overview shutdown.
   overview_session->UpdateMaskAndShadow();
 
-  Shell::Get()->NotifyOverviewModeEnding(overview_session);
+  for (auto& observer : observers_)
+    observer.OnOverviewModeEnding(overview_session);
   overview_session->Shutdown();
   // Don't delete |overview_session_| yet since the stack is still using it.
   base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, overview_session);
   last_selection_time_ = base::Time::Now();
-  Shell::Get()->NotifyOverviewModeEnded();
+  for (auto& observer : observers_)
+    observer.OnOverviewModeEnded();
   if (delayed_animations_.empty())
     OnEndingAnimationComplete(/*canceled=*/false);
 }
@@ -625,6 +627,17 @@
   return overview_blur_controller_->has_blur_animation();
 }
 
+std::vector<aura::Window*>
+OverviewController::GetWindowsListInOverviewGridsForTest() {
+  std::vector<aura::Window*> windows;
+  for (const std::unique_ptr<OverviewGrid>& grid :
+       overview_session_->grid_list_for_testing()) {
+    for (const auto& overview_session_item : grid->window_list())
+      windows.push_back(overview_session_item->GetWindow());
+  }
+  return windows;
+}
+
 void OverviewController::AddStartAnimationObserver(
     std::unique_ptr<DelayedAnimationObserver> animation_observer) {
   animation_observer->SetOwner(this);
diff --git a/ash/wm/overview/overview_controller.h b/ash/wm/overview/overview_controller.h
index 21fd918..6e9b13c 100644
--- a/ash/wm/overview/overview_controller.h
+++ b/ash/wm/overview/overview_controller.h
@@ -10,9 +10,11 @@
 
 #include "ash/ash_export.h"
 #include "ash/wm/overview/overview_delegate.h"
+#include "ash/wm/overview/overview_observer.h"
 #include "ash/wm/overview/overview_session.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
 #include "base/time/time.h"
 #include "ui/aura/window_occlusion_tracker.h"
 
@@ -69,9 +71,8 @@
   void PauseOcclusionTracker();
   void UnpauseOcclusionTracker(int delay);
 
-  // Gets the windows list that are shown in the overview windows grids if the
-  // overview mode is active for testing.
-  std::vector<aura::Window*> GetWindowsListInOverviewGridsForTesting();
+  void AddObserver(OverviewObserver* observer);
+  void RemoveObserver(OverviewObserver* observer);
 
   // Post a task to update the shadow and mask of overview windows.
   void DelayedUpdateMaskAndShadow();
@@ -107,6 +108,10 @@
   bool HasBlurForTest() const;
   bool HasBlurAnimationForTest() const;
 
+  // Gets the windows list that are shown in the overview windows grids if the
+  // overview mode is active for testing.
+  std::vector<aura::Window*> GetWindowsListInOverviewGridsForTest();
+
  private:
   class OverviewBlurController;
   friend class OverviewSessionTest;
@@ -148,6 +153,8 @@
 
   base::CancelableOnceClosure reset_pauser_task_;
 
+  base::ObserverList<OverviewObserver> observers_;
+
   base::WeakPtrFactory<OverviewController> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(OverviewController);
diff --git a/ash/wm/overview/overview_controller_unittest.cc b/ash/wm/overview/overview_controller_unittest.cc
index c1f8be6..3d2b2ad 100644
--- a/ash/wm/overview/overview_controller_unittest.cc
+++ b/ash/wm/overview/overview_controller_unittest.cc
@@ -6,9 +6,9 @@
 
 #include "ash/app_list/test/app_list_test_helper.h"
 #include "ash/shell.h"
-#include "ash/shell_observer.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wallpaper/wallpaper_widget_controller.h"
+#include "ash/wm/overview/overview_observer.h"
 #include "ash/wm/overview/overview_session.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
 #include "ash/wm/window_resizer.h"
@@ -34,7 +34,7 @@
   return location;
 }
 
-class TestShellObserver : public ShellObserver {
+class TestOverviewObserver : public OverviewObserver {
  public:
   enum AnimationState {
     UNKNOWN,
@@ -42,13 +42,15 @@
     CANCELED,
   };
 
-  explicit TestShellObserver(bool should_monitor_animation_state)
+  explicit TestOverviewObserver(bool should_monitor_animation_state)
       : should_monitor_animation_state_(should_monitor_animation_state) {
-    Shell::Get()->AddShellObserver(this);
+    Shell::Get()->overview_controller()->AddObserver(this);
   }
-  ~TestShellObserver() override { Shell::Get()->RemoveShellObserver(this); }
+  ~TestOverviewObserver() override {
+    Shell::Get()->overview_controller()->RemoveObserver(this);
+  }
 
-  // ShellObserver:
+  // OverviewObserver:
   void OnOverviewModeStarting() override {
     UpdateLastAnimationWasSlide(
         Shell::Get()->overview_controller()->overview_session());
@@ -121,7 +123,7 @@
 
   std::unique_ptr<base::RunLoop> run_loop_;
 
-  DISALLOW_COPY_AND_ASSIGN(TestShellObserver);
+  DISALLOW_COPY_AND_ASSIGN(TestOverviewObserver);
 };
 
 void WaitForOcclusionStateChange(aura::Window* window) {
@@ -158,12 +160,13 @@
 TEST_F(OverviewControllerTest, AnimationCallbacks) {
   ui::ScopedAnimationDurationScaleMode non_zero(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
-  TestShellObserver observer(/*should_monitor_animation_state = */ true);
+  TestOverviewObserver observer(/*should_monitor_animation_state = */ true);
   // Enter without windows.
   auto* shell = Shell::Get();
   shell->overview_controller()->ToggleOverview();
   EXPECT_TRUE(shell->overview_controller()->IsSelecting());
-  EXPECT_EQ(TestShellObserver::COMPLETED, observer.starting_animation_state());
+  EXPECT_EQ(TestOverviewObserver::COMPLETED,
+            observer.starting_animation_state());
   auto* overview_controller = shell->overview_controller();
   EXPECT_TRUE(overview_controller->HasBlurForTest());
   EXPECT_TRUE(overview_controller->HasBlurAnimationForTest());
@@ -171,12 +174,12 @@
   // Exit without windows still creates an animation.
   shell->overview_controller()->ToggleOverview();
   EXPECT_FALSE(shell->overview_controller()->IsSelecting());
-  EXPECT_EQ(TestShellObserver::UNKNOWN, observer.ending_animation_state());
+  EXPECT_EQ(TestOverviewObserver::UNKNOWN, observer.ending_animation_state());
   EXPECT_TRUE(overview_controller->HasBlurForTest());
   EXPECT_TRUE(overview_controller->HasBlurAnimationForTest());
 
   observer.WaitForEndingAnimationComplete();
-  EXPECT_EQ(TestShellObserver::COMPLETED, observer.ending_animation_state());
+  EXPECT_EQ(TestOverviewObserver::COMPLETED, observer.ending_animation_state());
   EXPECT_FALSE(overview_controller->HasBlurForTest());
   EXPECT_FALSE(overview_controller->HasBlurAnimationForTest());
 
@@ -187,23 +190,24 @@
       CreateTestWindowInShellWithBounds(bounds));
 
   observer.Reset();
-  ASSERT_EQ(TestShellObserver::UNKNOWN, observer.starting_animation_state());
-  ASSERT_EQ(TestShellObserver::UNKNOWN, observer.ending_animation_state());
+  ASSERT_EQ(TestOverviewObserver::UNKNOWN, observer.starting_animation_state());
+  ASSERT_EQ(TestOverviewObserver::UNKNOWN, observer.ending_animation_state());
 
   // Enter with windows.
   shell->overview_controller()->ToggleOverview();
   EXPECT_TRUE(shell->overview_controller()->IsSelecting());
-  EXPECT_EQ(TestShellObserver::UNKNOWN, observer.starting_animation_state());
-  EXPECT_EQ(TestShellObserver::UNKNOWN, observer.ending_animation_state());
+  EXPECT_EQ(TestOverviewObserver::UNKNOWN, observer.starting_animation_state());
+  EXPECT_EQ(TestOverviewObserver::UNKNOWN, observer.ending_animation_state());
   EXPECT_FALSE(overview_controller->HasBlurForTest());
   EXPECT_FALSE(overview_controller->HasBlurAnimationForTest());
 
   // Exit with windows before starting animation ends.
   shell->overview_controller()->ToggleOverview();
   EXPECT_FALSE(shell->overview_controller()->IsSelecting());
-  EXPECT_EQ(TestShellObserver::CANCELED, observer.starting_animation_state());
+  EXPECT_EQ(TestOverviewObserver::CANCELED,
+            observer.starting_animation_state());
   // No shield so ending animation ends immediately.
-  EXPECT_EQ(TestShellObserver::COMPLETED, observer.ending_animation_state());
+  EXPECT_EQ(TestOverviewObserver::COMPLETED, observer.ending_animation_state());
   // Blur animation never started.
   EXPECT_FALSE(overview_controller->HasBlurForTest());
   EXPECT_FALSE(overview_controller->HasBlurAnimationForTest());
@@ -213,8 +217,8 @@
   // Enter again before exit animation ends.
   shell->overview_controller()->ToggleOverview();
   EXPECT_TRUE(shell->overview_controller()->IsSelecting());
-  EXPECT_EQ(TestShellObserver::UNKNOWN, observer.ending_animation_state());
-  EXPECT_EQ(TestShellObserver::UNKNOWN, observer.starting_animation_state());
+  EXPECT_EQ(TestOverviewObserver::UNKNOWN, observer.ending_animation_state());
+  EXPECT_EQ(TestOverviewObserver::UNKNOWN, observer.starting_animation_state());
   // Blur animation will start when animation is completed.
   EXPECT_FALSE(overview_controller->HasBlurForTest());
   EXPECT_FALSE(overview_controller->HasBlurAnimationForTest());
@@ -222,7 +226,8 @@
   // Activating window while entering animation should cancel the overview.
   wm::ActivateWindow(window1.get());
   EXPECT_FALSE(shell->overview_controller()->IsSelecting());
-  EXPECT_EQ(TestShellObserver::CANCELED, observer.starting_animation_state());
+  EXPECT_EQ(TestOverviewObserver::CANCELED,
+            observer.starting_animation_state());
   // Blur animation never started.
   EXPECT_FALSE(overview_controller->HasBlurForTest());
   EXPECT_FALSE(overview_controller->HasBlurAnimationForTest());
@@ -230,7 +235,7 @@
 
 // Tests the slide animation for overview is never used in clamshell.
 TEST_F(OverviewControllerTest, OverviewEnterExitAnimationClamshell) {
-  TestShellObserver observer(/*should_monitor_animation_state = */ false);
+  TestOverviewObserver observer(/*should_monitor_animation_state = */ false);
 
   const gfx::Rect bounds(200, 200);
   std::unique_ptr<aura::Window> window(
@@ -253,7 +258,7 @@
 // are minimized, and that if overview is exited from the home launcher all
 // windows are minimized.
 TEST_F(OverviewControllerTest, OverviewEnterExitAnimationTablet) {
-  TestShellObserver observer(/*should_monitor_animation_state = */ false);
+  TestOverviewObserver observer(/*should_monitor_animation_state = */ false);
 
   // Ensure calls to EnableTabletModeWindowManager complete.
   base::RunLoop().RunUntilIdle();
@@ -286,7 +291,7 @@
   Shell::Get()
       ->overview_controller()
       ->set_occlusion_pause_duration_for_end_ms_for_test(100);
-  TestShellObserver observer(/*should_monitor_animation_state = */ true);
+  TestOverviewObserver observer(/*should_monitor_animation_state = */ true);
   ui::ScopedAnimationDurationScaleMode non_zero(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
   gfx::Rect bounds(0, 0, 100, 100);
diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc
index f8f2634..ef7863a 100644
--- a/ash/wm/overview/overview_item.cc
+++ b/ash/wm/overview/overview_item.cc
@@ -860,10 +860,6 @@
   widget_window->layer()->SetOpacity(header_opacity);
 }
 
-aura::Window* OverviewItem::GetOverviewWindowForMinimizedStateForTest() {
-  return transform_window_.GetOverviewWindowForMinimizedState();
-}
-
 void OverviewItem::StartDrag() {
   overview_grid_->SetSelectionWidgetVisibility(false);
 
diff --git a/ash/wm/overview/overview_item.h b/ash/wm/overview/overview_item.h
index 123048a..6038aa7 100644
--- a/ash/wm/overview/overview_item.h
+++ b/ash/wm/overview/overview_item.h
@@ -273,8 +273,6 @@
   // Allows a test to directly set animation state.
   gfx::SlideAnimation* GetBackgroundViewAnimation();
 
-  aura::Window* GetOverviewWindowForMinimizedStateForTest();
-
   // Called before dragging. Scales up the window a little bit to indicate its
   // selection and stacks the window at the top of the Z order in order to keep
   // it visible while dragging around.
diff --git a/ash/wm/overview/overview_observer.h b/ash/wm/overview/overview_observer.h
new file mode 100644
index 0000000..d4408c3
--- /dev/null
+++ b/ash/wm/overview/overview_observer.h
@@ -0,0 +1,41 @@
+// Copyright 2019 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_WM_OVERVIEW_OVERVIEW_OBSERVER_H_
+#define ASH_WM_OVERVIEW_OVERVIEW_OBSERVER_H_
+
+#include "ash/ash_export.h"
+#include "base/observer_list_types.h"
+
+namespace ash {
+class OverviewSession;
+
+// Used to observe overview mode changes in ash.
+class ASH_EXPORT OverviewObserver : public base::CheckedObserver {
+ public:
+  // Called when the overview mode is about to be started (before the windows
+  // get re-arranged).
+  virtual void OnOverviewModeStarting() {}
+
+  // Called after the animations that happen when overview mode is started are
+  // complete. If |canceled| it means overview was quit before the start
+  // animations were finished.
+  virtual void OnOverviewModeStartingAnimationComplete(bool canceled) {}
+
+  // Called when the overview mode is about to end (before the windows restore
+  // themselves). |overview_session| will not be null.
+  virtual void OnOverviewModeEnding(OverviewSession* overview_session) {}
+
+  // Called after overview mode has ended.
+  virtual void OnOverviewModeEnded() {}
+
+  // Called after the animations that happen when overview mode is ended are
+  // complete. If |canceled| it means overview was reentered before the exit
+  // animations were finished.
+  virtual void OnOverviewModeEndingAnimationComplete(bool canceled) {}
+};
+
+}  // namespace ash
+
+#endif  // ASH_WM_OVERVIEW_OVERVIEW_OBSERVER_H_
\ No newline at end of file
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index f632e81..f823d52 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -168,7 +168,8 @@
   aura::Window* GetOverviewWindowForMinimizedState(int index,
                                                    aura::Window* window) {
     OverviewItem* item = GetWindowItemForWindow(index, window);
-    return item->GetOverviewWindowForMinimizedStateForTest();
+    views::Widget* widget = minimized_widget(item);
+    return widget ? widget->GetNativeWindow() : nullptr;
   }
 
   gfx::Rect GetTransformedBounds(aura::Window* window) {
@@ -3697,7 +3698,7 @@
   EXPECT_TRUE(split_view_controller()->IsSplitViewModeActive());
   ASSERT_TRUE(split_view_controller()->split_view_divider());
   std::vector<aura::Window*> window_list =
-      overview_controller()->GetWindowsListInOverviewGridsForTesting();
+      overview_controller()->GetWindowsListInOverviewGridsForTest();
   EXPECT_EQ(2u, window_list.size());
   EXPECT_FALSE(base::ContainsValue(window_list, window1.get()));
   EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
@@ -3713,8 +3714,7 @@
   // the overview list.
   EXPECT_TRUE(IsSelecting());
   EXPECT_FALSE(split_view_controller()->IsSplitViewModeActive());
-  window_list =
-      overview_controller()->GetWindowsListInOverviewGridsForTesting();
+  window_list = overview_controller()->GetWindowsListInOverviewGridsForTest();
   EXPECT_EQ(3u, window_list.size());
   EXPECT_TRUE(base::ContainsValue(window_list, window1.get()));
   EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
diff --git a/ash/wm/overview/scoped_overview_transform_window.cc b/ash/wm/overview/scoped_overview_transform_window.cc
index d6e69f3..8b0f53a 100644
--- a/ash/wm/overview/scoped_overview_transform_window.cc
+++ b/ash/wm/overview/scoped_overview_transform_window.cc
@@ -287,8 +287,10 @@
     if (window->Contains(target))
       return true;
   }
-  aura::Window* mirror = GetOverviewWindowForMinimizedState();
-  return mirror && mirror->Contains(target);
+
+  if (!minimized_widget_)
+    return false;
+  return minimized_widget_->GetNativeWindow()->Contains(target);
 }
 
 gfx::Rect ScopedOverviewTransformWindow::GetTransformedBounds() const {
@@ -440,7 +442,7 @@
 
 aura::Window* ScopedOverviewTransformWindow::GetOverviewWindow() const {
   if (minimized_widget_)
-    return GetOverviewWindowForMinimizedState();
+    return minimized_widget_->GetNativeWindow();
   return window_;
 }
 
@@ -448,11 +450,6 @@
   original_opacity_ = 1.f;
 }
 
-aura::Window*
-ScopedOverviewTransformWindow::GetOverviewWindowForMinimizedState() const {
-  return minimized_widget_ ? minimized_widget_->GetNativeWindow() : nullptr;
-}
-
 void ScopedOverviewTransformWindow::UpdateWindowDimensionsType() {
   type_ = GetWindowDimensionsType(window_);
   overview_bounds_.reset();
diff --git a/ash/wm/overview/scoped_overview_transform_window.h b/ash/wm/overview/scoped_overview_transform_window.h
index 00596ca..464f1bfd 100644
--- a/ash/wm/overview/scoped_overview_transform_window.h
+++ b/ash/wm/overview/scoped_overview_transform_window.h
@@ -153,10 +153,6 @@
   // Ensures that a window is visible by setting its opacity to 1.
   void EnsureVisible();
 
-  // Returns an overview window created for minimized window, or nullptr if it
-  // does not exist.
-  aura::Window* GetOverviewWindowForMinimizedState() const;
-
   // Called via OverviewItem from OverviewGrid when |window_|'s bounds
   // change. Must be called before PositionWindows in OverviewGrid.
   void UpdateWindowDimensionsType();
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index c51fa1d..5d207ce 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -244,6 +244,7 @@
   if (state_ == NO_SNAP) {
     // Add observers when the split view mode starts.
     Shell::Get()->AddShellObserver(this);
+    Shell::Get()->overview_controller()->AddObserver(this);
     Shell::Get()->activation_client()->AddObserver(this);
     Shell::Get()->NotifySplitViewModeStarting();
 
@@ -521,6 +522,7 @@
 
   // Remove observers when the split view mode ends.
   Shell::Get()->RemoveShellObserver(this);
+  Shell::Get()->overview_controller()->RemoveObserver(this);
   Shell::Get()->activation_client()->RemoveObserver(this);
 
   StopObserving(LEFT);
diff --git a/ash/wm/splitview/split_view_controller.h b/ash/wm/splitview/split_view_controller.h
index 7a80ccf..1e06f2f 100644
--- a/ash/wm/splitview/split_view_controller.h
+++ b/ash/wm/splitview/split_view_controller.h
@@ -11,6 +11,7 @@
 #include "ash/public/interfaces/split_view.mojom.h"
 #include "ash/session/session_observer.h"
 #include "ash/shell_observer.h"
+#include "ash/wm/overview/overview_observer.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_observer.h"
 #include "ash/wm/window_state_observer.h"
@@ -46,6 +47,7 @@
                                        public ash::wm::WindowStateObserver,
                                        public ::wm::ActivationChangeObserver,
                                        public ShellObserver,
+                                       public OverviewObserver,
                                        public display::DisplayObserver,
                                        public TabletModeObserver,
                                        public AccessibilityObserver,
@@ -171,6 +173,8 @@
 
   // ShellObserver:
   void OnPinnedStateChanged(aura::Window* pinned_window) override;
+
+  // OverviewObserver:
   void OnOverviewModeStarting() override;
   void OnOverviewModeEnding(OverviewSession* overview_session) override;
 
diff --git a/ash/wm/splitview/split_view_controller_unittest.cc b/ash/wm/splitview/split_view_controller_unittest.cc
index 6561db5..df999c8 100644
--- a/ash/wm/splitview/split_view_controller_unittest.cc
+++ b/ash/wm/splitview/split_view_controller_unittest.cc
@@ -25,6 +25,7 @@
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_item.h"
+#include "ash/wm/overview/overview_observer.h"
 #include "ash/wm/splitview/split_view_divider.h"
 #include "ash/wm/splitview/split_view_drag_indicators.h"
 #include "ash/wm/splitview/split_view_utils.h"
@@ -57,17 +58,17 @@
 namespace {
 
 // The observer to observe the overview states in |root_window_|.
-class OverviewStatesObserver : public ShellObserver {
+class OverviewStatesObserver : public OverviewObserver {
  public:
   OverviewStatesObserver(aura::Window* root_window)
       : root_window_(root_window) {
-    Shell::Get()->AddShellObserver(this);
+    Shell::Get()->overview_controller()->AddObserver(this);
   }
   ~OverviewStatesObserver() override {
-    Shell::Get()->RemoveShellObserver(this);
+    Shell::Get()->overview_controller()->RemoveObserver(this);
   }
 
-  // ShellObserver:
+  // OverviewObserver:
   void OnOverviewModeStarting() override {
     // Reset the value to true.
     overview_animate_when_exiting_ = true;
@@ -155,7 +156,7 @@
   std::vector<aura::Window*> GetWindowsInOverviewGrids() {
     return Shell::Get()
         ->overview_controller()
-        ->GetWindowsListInOverviewGridsForTesting();
+        ->GetWindowsListInOverviewGridsForTest();
   }
 
   SplitViewController* split_view_controller() {
@@ -1825,15 +1826,15 @@
   EXPECT_TRUE(window->layer()->GetTargetTransform().IsIdentity());
 }
 
-// TestShellObserver which tracks how many overview items there are when
+// TestOverviewObserver which tracks how many overview items there are when
 // overview mode is about to end.
-class TestOverviewItemsOnOverviewModeEndObserver : public ShellObserver {
+class TestOverviewItemsOnOverviewModeEndObserver : public OverviewObserver {
  public:
   TestOverviewItemsOnOverviewModeEndObserver() {
-    Shell::Get()->AddShellObserver(this);
+    Shell::Get()->overview_controller()->AddObserver(this);
   }
   ~TestOverviewItemsOnOverviewModeEndObserver() override {
-    Shell::Get()->RemoveShellObserver(this);
+    Shell::Get()->overview_controller()->RemoveObserver(this);
   }
   void OnOverviewModeEnding(OverviewSession* overview_session) override {
     items_on_last_overview_end_ = overview_session->num_items_for_testing();
diff --git a/ash/wm/tablet_mode/tablet_mode_observer.h b/ash/wm/tablet_mode/tablet_mode_observer.h
index 18575ce..80eec39 100644
--- a/ash/wm/tablet_mode/tablet_mode_observer.h
+++ b/ash/wm/tablet_mode/tablet_mode_observer.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef ASH_TABLET_MODE_TABLET_MODE_OBSERVER_H_
-#define ASH_TABLET_MODE_TABLET_MODE_OBSERVER_H_
+#ifndef ASH_WM_TABLET_MODE_TABLET_MODE_OBSERVER_H_
+#define ASH_WM_TABLET_MODE_TABLET_MODE_OBSERVER_H_
 
 #include "ash/ash_export.h"
 
@@ -39,4 +39,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_TABLET_MODE_TABLET_MODE_OBSERVER_H_
+#endif  // ASH_WM_TABLET_MODE_TABLET_MODE_OBSERVER_H_
diff --git a/ash/wm/tablet_mode/tablet_mode_window_manager.cc b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
index daa265a..f8da6635 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_manager.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
@@ -48,6 +48,7 @@
     window->RemoveObserver(this);
   added_windows_.clear();
   Shell::Get()->RemoveShellObserver(this);
+  Shell::Get()->overview_controller()->RemoveObserver(this);
   display::Screen::GetScreen()->RemoveObserver(this);
   Shell::Get()->split_view_controller()->RemoveObserver(this);
   EnableBackdropBehindTopWindowOnEachDisplay(false);
@@ -84,6 +85,35 @@
     window_state_map_.erase(it);
 }
 
+void TabletModeWindowManager::OnSplitViewModeEnded() {
+  switch (Shell::Get()->split_view_controller()->end_reason()) {
+    case SplitViewController::EndReason::kNormal:
+    case SplitViewController::EndReason::kUnsnappableWindowActivated:
+      break;
+    case SplitViewController::EndReason::kHomeLauncherPressed:
+    case SplitViewController::EndReason::kActiveUserChanged:
+      // For the case of kHomeLauncherPressed, the home launcher will minimize
+      // the snapped windows after ending splitview, so avoid maximizing them
+      // here. For the case of kActiveUserChanged, the snapped windows will be
+      // used to restore the splitview layout when switching back, and it is
+      // already too late to maximize them anyway (the for loop below would
+      // iterate over windows in the newly activated user session).
+      return;
+  }
+
+  // Maximize all snapped windows upon exiting split view mode. Note the snapped
+  // window might not be tracked in our |window_state_map_|.
+  MruWindowTracker::WindowList windows =
+      Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal();
+  for (auto* window : windows) {
+    wm::WindowState* window_state = wm::GetWindowState(window);
+    if (window_state->IsSnapped()) {
+      wm::WMEvent event(wm::WM_EVENT_MAXIMIZE);
+      window_state->OnWMEvent(&event);
+    }
+  }
+}
+
 void TabletModeWindowManager::OnOverviewModeStarting() {
   for (auto& pair : window_state_map_)
     SetDeferBoundsUpdates(pair.first, /*defer_bounds_updates=*/true);
@@ -117,35 +147,6 @@
   }
 }
 
-void TabletModeWindowManager::OnSplitViewModeEnded() {
-  switch (Shell::Get()->split_view_controller()->end_reason()) {
-    case SplitViewController::EndReason::kNormal:
-    case SplitViewController::EndReason::kUnsnappableWindowActivated:
-      break;
-    case SplitViewController::EndReason::kHomeLauncherPressed:
-    case SplitViewController::EndReason::kActiveUserChanged:
-      // For the case of kHomeLauncherPressed, the home launcher will minimize
-      // the snapped windows after ending splitview, so avoid maximizing them
-      // here. For the case of kActiveUserChanged, the snapped windows will be
-      // used to restore the splitview layout when switching back, and it is
-      // already too late to maximize them anyway (the for loop below would
-      // iterate over windows in the newly activated user session).
-      return;
-  }
-
-  // Maximize all snapped windows upon exiting split view mode. Note the snapped
-  // window might not be tracked in our |window_state_map_|.
-  MruWindowTracker::WindowList windows =
-      Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal();
-  for (auto* window : windows) {
-    wm::WindowState* window_state = wm::GetWindowState(window);
-    if (window_state->IsSnapped()) {
-      wm::WMEvent event(wm::WM_EVENT_MAXIMIZE);
-      window_state->OnWMEvent(&event);
-    }
-  }
-}
-
 void TabletModeWindowManager::OnWindowDestroying(aura::Window* window) {
   if (IsContainerWindow(window)) {
     // container window can be removed on display destruction.
@@ -289,6 +290,7 @@
   EnableBackdropBehindTopWindowOnEachDisplay(true);
   display::Screen::GetScreen()->AddObserver(this);
   Shell::Get()->AddShellObserver(this);
+  Shell::Get()->overview_controller()->AddObserver(this);
   Shell::Get()->split_view_controller()->AddObserver(this);
   event_handler_ = std::make_unique<wm::TabletModeEventHandler>();
 }
diff --git a/ash/wm/tablet_mode/tablet_mode_window_manager.h b/ash/wm/tablet_mode/tablet_mode_window_manager.h
index 98227bd..a2c93af 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_manager.h
+++ b/ash/wm/tablet_mode/tablet_mode_window_manager.h
@@ -12,6 +12,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/shell_observer.h"
+#include "ash/wm/overview/overview_observer.h"
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/window_state.h"
 #include "base/macros.h"
@@ -40,6 +41,7 @@
     : public aura::WindowObserver,
       public display::DisplayObserver,
       public ShellObserver,
+      public OverviewObserver,
       public SplitViewController::Observer {
  public:
   // This should only be deleted by the creator (ash::Shell).
@@ -58,10 +60,12 @@
   void WindowStateDestroyed(aura::Window* window);
 
   // ShellObserver:
+  void OnSplitViewModeEnded() override;
+
+  // OverviewObserver:
   void OnOverviewModeStarting() override;
   void OnOverviewModeEnding(OverviewSession* overview_session) override;
   void OnOverviewModeEnded() override;
-  void OnSplitViewModeEnded() override;
 
   // aura::WindowObserver:
   void OnWindowDestroying(aura::Window* window) override;
diff --git a/ash/wm/workspace/backdrop_controller.cc b/ash/wm/workspace/backdrop_controller.cc
index bfb4185..26297d1 100644
--- a/ash/wm/workspace/backdrop_controller.cc
+++ b/ash/wm/workspace/backdrop_controller.cc
@@ -73,6 +73,7 @@
     : container_(container) {
   DCHECK(container_);
   Shell::Get()->AddShellObserver(this);
+  Shell::Get()->overview_controller()->AddObserver(this);
   Shell::Get()->accessibility_controller()->AddObserver(this);
   Shell::Get()->app_list_controller()->AddObserver(this);
   Shell::Get()->wallpaper_controller()->AddObserver(this);
@@ -81,6 +82,8 @@
 BackdropController::~BackdropController() {
   Shell::Get()->accessibility_controller()->RemoveObserver(this);
   Shell::Get()->wallpaper_controller()->RemoveObserver(this);
+  if (Shell::Get()->overview_controller())
+    Shell::Get()->overview_controller()->RemoveObserver(this);
   Shell::Get()->RemoveShellObserver(this);
   // AppListController is destroyed early when Shell is being destroyed, it may
   // not exist.
@@ -159,6 +162,14 @@
   container_->StackChildAbove(window, backdrop_window_);
 }
 
+void BackdropController::OnSplitViewModeStarting() {
+  Shell::Get()->split_view_controller()->AddObserver(this);
+}
+
+void BackdropController::OnSplitViewModeEnded() {
+  Shell::Get()->split_view_controller()->RemoveObserver(this);
+}
+
 void BackdropController::OnOverviewModeStarting() {
   if (backdrop_window_)
     backdrop_window_->SetProperty(aura::client::kAnimationsDisabledKey, true);
@@ -177,14 +188,6 @@
     backdrop_window_->ClearProperty(aura::client::kAnimationsDisabledKey);
 }
 
-void BackdropController::OnSplitViewModeStarting() {
-  Shell::Get()->split_view_controller()->AddObserver(this);
-}
-
-void BackdropController::OnSplitViewModeEnded() {
-  Shell::Get()->split_view_controller()->RemoveObserver(this);
-}
-
 void BackdropController::OnAppListVisibilityChanged(bool shown,
                                                     int64_t display_id) {
   UpdateBackdrop();
diff --git a/ash/wm/workspace/backdrop_controller.h b/ash/wm/workspace/backdrop_controller.h
index 0bb2c41..00afed3 100644
--- a/ash/wm/workspace/backdrop_controller.h
+++ b/ash/wm/workspace/backdrop_controller.h
@@ -11,6 +11,7 @@
 #include "ash/app_list/app_list_controller_observer.h"
 #include "ash/shell_observer.h"
 #include "ash/wallpaper/wallpaper_controller_observer.h"
+#include "ash/wm/overview/overview_observer.h"
 #include "ash/wm/splitview/split_view_controller.h"
 #include "base/macros.h"
 
@@ -47,6 +48,7 @@
 class BackdropController : public AccessibilityObserver,
                            public AppListControllerObserver,
                            public ShellObserver,
+                           public OverviewObserver,
                            public SplitViewController::Observer,
                            public WallpaperControllerObserver {
  public:
@@ -70,11 +72,13 @@
   aura::Window* backdrop_window() { return backdrop_window_; }
 
   // ShellObserver:
+  void OnSplitViewModeStarting() override;
+  void OnSplitViewModeEnded() override;
+
+  // OverviewObserver:
   void OnOverviewModeStarting() override;
   void OnOverviewModeEnding(OverviewSession* overview_session) override;
   void OnOverviewModeEndingAnimationComplete(bool canceled) override;
-  void OnSplitViewModeStarting() override;
-  void OnSplitViewModeEnded() override;
 
   // AppListControllerObserver:
   void OnAppListVisibilityChanged(bool shown, int64_t display_id) override;
diff --git a/base/allocator/allocator_extension.cc b/base/allocator/allocator_extension.cc
index e10a92c..fc1fc50 100644
--- a/base/allocator/allocator_extension.cc
+++ b/base/allocator/allocator_extension.cc
@@ -34,6 +34,19 @@
   return false;
 }
 
+bool SetNumericProperty(const char* name, size_t value) {
+#if defined(USE_TCMALLOC)
+  return ::MallocExtension::instance()->SetNumericProperty(name, value);
+#endif
+  return false;
+}
+
+void GetHeapSample(std::string* writer) {
+#if defined(USE_TCMALLOC)
+  ::MallocExtension::instance()->GetHeapSample(writer);
+#endif
+}
+
 bool IsHeapProfilerRunning() {
 #if defined(USE_TCMALLOC)
   return ::IsHeapProfilerRunning();
diff --git a/base/allocator/allocator_extension.h b/base/allocator/allocator_extension.h
index 9f2775a..6330de8c 100644
--- a/base/allocator/allocator_extension.h
+++ b/base/allocator/allocator_extension.h
@@ -6,6 +6,7 @@
 #define BASE_ALLOCATOR_ALLOCATOR_EXTENSION_H_
 
 #include <stddef.h> // for size_t
+#include <string>
 
 #include "base/base_export.h"
 #include "build/build_config.h"
@@ -27,6 +28,21 @@
 // |name| or |value| cannot be NULL
 BASE_EXPORT bool GetNumericProperty(const char* name, size_t* value);
 
+// Set the named property's |value|. Returns true if the property is known and
+// writable. Returns false if the property is not a valid property name for the
+// current allocator implementation, or is not writable. |name| cannot be NULL.
+BASE_EXPORT bool SetNumericProperty(const char* name, size_t value);
+
+// Outputs to |writer| a sample of live objects and the stack traces
+// that allocated these objects.  The format of the returned output
+// is equivalent to the output of the heap profiler and can
+// therefore be passed to "pprof".
+// NOTE: by default, the allocator does not do any heap sampling, and this
+//       function will always return an empty sample.  To get useful
+//       data from GetHeapSample, you must also set the numeric property
+//       "tcmalloc.sampling_period_bytes" to a value such as 524288.
+BASE_EXPORT void GetHeapSample(std::string* writer);
+
 BASE_EXPORT bool IsHeapProfilerRunning();
 
 // Register callbacks for alloc and free. Can only store one callback at a time
diff --git a/base/task/sequence_manager/thread_controller_impl.cc b/base/task/sequence_manager/thread_controller_impl.cc
index 94f8ab3..01b4df7 100644
--- a/base/task/sequence_manager/thread_controller_impl.cc
+++ b/base/task/sequence_manager/thread_controller_impl.cc
@@ -203,9 +203,10 @@
 
     {
       TRACE_TASK_EXECUTION("ThreadControllerImpl::RunTask", *task);
-      // Trace-parsing tools (Lighthouse, etc) consume this event to determine
-      // long tasks. See https://crbug.com/874982
-      TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("lighthouse"), "RunTask");
+      // Trace-parsing tools (DevTools, Lighthouse, etc) consume this event
+      // to determine long tasks.
+      // See https://crbug.com/681863 and https://crbug.com/874982
+      TRACE_EVENT0("devtools.timeline", "RunTask");
       task_annotator_.RunTask("ThreadControllerImpl::RunTask", &*task);
     }
 
diff --git a/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc b/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
index 2082ad8..7ad868a 100644
--- a/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
+++ b/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
@@ -334,9 +334,10 @@
 
     work_id_provider_->IncrementWorkId();
     TRACE_TASK_EXECUTION("ThreadController::Task", *task);
-    // Trace-parsing tools (Lighthouse, etc) consume this event to determine
-    // long tasks. See https://crbug.com/874982
-    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("lighthouse"), "RunTask");
+    // Trace-parsing tools (DevTools, Lighthouse, etc) consume this event
+    // to determine long tasks.
+    // See https://crbug.com/681863 and https://crbug.com/874982
+    TRACE_EVENT0("devtools.timeline", "RunTask");
     task_annotator_.RunTask("ThreadController::Task", &*task);
     *ran_task = true;
 
diff --git a/base/trace_event/builtin_categories.h b/base/trace_event/builtin_categories.h
index 0e40b59..72adf46 100644
--- a/base/trace_event/builtin_categories.h
+++ b/base/trace_event/builtin_categories.h
@@ -176,7 +176,6 @@
   X(TRACE_DISABLED_BY_DEFAULT("gpu.service"))                            \
   X(TRACE_DISABLED_BY_DEFAULT("ipc.flow"))                               \
   X(TRACE_DISABLED_BY_DEFAULT("layer-element"))                          \
-  X(TRACE_DISABLED_BY_DEFAULT("lighthouse"))                             \
   X(TRACE_DISABLED_BY_DEFAULT("loading"))                                \
   X(TRACE_DISABLED_BY_DEFAULT("memory-infra"))                           \
   X(TRACE_DISABLED_BY_DEFAULT("memory-infra.v8.code_stats"))             \
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedImageLoader.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedImageLoader.java
index 1265b25..13bee77 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedImageLoader.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedImageLoader.java
@@ -35,6 +35,7 @@
  * Provides image loading and other host-specific asset fetches for Feed.
  */
 public class FeedImageLoader implements ImageLoaderApi {
+    private static final String CACHED_IMAGE_FETCHER_UMA_CLIENT_NAME = "Feed";
     private static final String ASSET_PREFIX = "asset://";
     private static final String OVERLAY_IMAGE_PREFIX = "overlay-image://";
     private static final String OVERLAY_IMAGE_URL_PARAM = "url";
@@ -168,7 +169,8 @@
 
     @VisibleForTesting
     protected void fetchImage(String url, int width, int height, Callback<Bitmap> callback) {
-        mCachedImageFetcher.fetchImage(url, width, height, callback);
+        mCachedImageFetcher.fetchImage(
+                url, CACHED_IMAGE_FETCHER_UMA_CLIENT_NAME, width, height, callback);
     }
 
     @VisibleForTesting
diff --git a/chrome/android/java/res/values-v17/styles.xml b/chrome/android/java/res/values-v17/styles.xml
index 9fb5403..9218420 100644
--- a/chrome/android/java/res/values-v17/styles.xml
+++ b/chrome/android/java/res/values-v17/styles.xml
@@ -208,10 +208,6 @@
         <item name="android:windowLightNavigationBar" tools:targetApi="28">false</item>
     </style>
 
-    <!-- TODO(huayinz): Temporary theme. Remove this. -->
-    <style name="DialogWhenLarge" parent="Base.Theme.Chromium.DialogWhenLarge"
-        tools:ignore="UnusedResources" />
-
     <!-- ThemeOverlay -->
     <style name="OverflowMenuThemeOverlay" parent="Theme.AppCompat.DayNight">
         <item name="android:popupBackground">@null</item>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java
index d7d773b8..1d5ce485 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java
@@ -48,6 +48,8 @@
 class AssistantDetailsViewBinder
         implements PropertyModelChangeProcessor.ViewBinder<AssistantDetailsModel,
                 AssistantDetailsViewBinder.ViewHolder, PropertyKey> {
+    private static final String CACHED_IMAGE_FETCHER_UMA_CLIENT_NAME = "AssistantDetails";
+
     private static final int IMAGE_BORDER_RADIUS = 4;
     private static final int PULSING_DURATION_MS = 1_000;
     private static final String DETAILS_TIME_FORMAT = "H:mma";
@@ -120,11 +122,12 @@
 
         // Download image and then set it in the model.
         if (!details.getUrl().isEmpty()) {
-            CachedImageFetcher.getInstance().fetchImage(details.getUrl(), image -> {
-                if (image != null) {
-                    viewHolder.mImageView.setImageDrawable(getRoundedImage(image));
-                }
-            });
+            CachedImageFetcher.getInstance().fetchImage(
+                    details.getUrl(), CACHED_IMAGE_FETCHER_UMA_CLIENT_NAME, image -> {
+                        if (image != null) {
+                            viewHolder.mImageView.setImageDrawable(getRoundedImage(image));
+                        }
+                    });
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcher.java b/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcher.java
index d4f27a2..20fde4e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcher.java
@@ -22,15 +22,17 @@
     /**
      * Report an event metric.
      *
+     * @param clientName Name of the cached image fetcher client to report UMA metrics for.
      * @param eventId The event to be reported
      */
-    void reportEvent(@CachedImageFetcherEvent int eventId);
+    void reportEvent(String clientName, @CachedImageFetcherEvent int eventId);
 
     /**
      * Fetches the image at url with the desired size. Image is null if not
      * found or fails decoding.
      *
      * @param url The url to fetch the image from.
+     * @param clientName Name of the cached image fetcher client to report UMA metrics for.
      * @param width The new bitmap's desired width (in pixels). If the given value is <= 0, the
      * image won't be scaled.
      * @param height The new bitmap's desired height (in pixels). If the given value is <= 0, the
@@ -38,16 +40,18 @@
      * @param callback The function which will be called when the image is ready; will be called
      * with null result if fetching fails;
      */
-    void fetchImage(String url, int width, int height, Callback<Bitmap> callback);
+    void fetchImage(
+            String url, String clientName, int width, int height, Callback<Bitmap> callback);
 
     /**
      * Alias of fetchImage that ignores scaling.
      *
      * @param url The url to fetch the image from.
+     * @param clientName Name of the cached image fetcher client to report UMA metrics for.
      * @param callback The function which will be called when the image is ready; will be called
      * with null result if fetching fails;
      */
-    void fetchImage(String url, Callback<Bitmap> callback);
+    void fetchImage(String url, String clientName, Callback<Bitmap> callback);
 
     /**
      * Destroy method, called to clear resources to prevent leakage.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherBridge.java
index 8fd8d71..0bb518d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherBridge.java
@@ -47,34 +47,35 @@
      * Fetch the image from native.
      *
      * @param url The url to fetch.
-     * @param width The width to use when resizing the image.
-     * @param height The height to use when resizing the image.
+     * @param clientName The UMA client name to report the metrics to.
      * @param callback The callback to call when the image is ready.
      */
-    public void fetchImage(String url, int width, int height, Callback<Bitmap> callback) {
+    public void fetchImage(String url, String clientName, Callback<Bitmap> callback) {
         assert mNativeCachedImageFetcherBridge != 0;
-        nativeFetchImage(mNativeCachedImageFetcherBridge, url, width, height, callback);
+        nativeFetchImage(mNativeCachedImageFetcherBridge, url, clientName, callback);
     }
 
     /**
      * Report a metrics event.
      *
+     * @param clientName The UMA client name to report the metrics to.
      * @param eventId The event to report.
      */
-    public void reportEvent(@CachedImageFetcherEvent int eventId) {
+    public void reportEvent(String clientName, @CachedImageFetcherEvent int eventId) {
         assert mNativeCachedImageFetcherBridge != 0;
-        nativeReportEvent(mNativeCachedImageFetcherBridge, eventId);
+        nativeReportEvent(mNativeCachedImageFetcherBridge, clientName, eventId);
     }
 
     /**
      * Report a timing event for a cache hit.
      *
+     * @param clientName The UMA client name to report the metrics to.
      * @param startTimeMillis The start time (in milliseconds) of the request, used to measure the
      * total duration.
      */
-    public void reportCacheHitTime(long startTimeMillis) {
+    public void reportCacheHitTime(String clientName, long startTimeMillis) {
         assert mNativeCachedImageFetcherBridge != 0;
-        nativeReportCacheHitTime(mNativeCachedImageFetcherBridge, startTimeMillis);
+        nativeReportCacheHitTime(mNativeCachedImageFetcherBridge, clientName, startTimeMillis);
     }
 
     // Native methods
@@ -82,8 +83,9 @@
     private native void nativeDestroy(long nativeCachedImageFetcherBridge);
     private native String nativeGetFilePath(long nativeCachedImageFetcherBridge, String url);
     private native void nativeFetchImage(long nativeCachedImageFetcherBridge, String url,
-            int widthPx, int heightPx, Callback<Bitmap> callback);
-    private native void nativeReportEvent(long nativeCachedImageFetcherBridge, int eventId);
+            String clientName, Callback<Bitmap> callback);
+    private native void nativeReportEvent(
+            long nativeCachedImageFetcherBridge, String clientName, int eventId);
     private native void nativeReportCacheHitTime(
-            long nativeCachedImageFetcherBridge, long startTimeMillis);
+            long nativeCachedImageFetcherBridge, String clientName, long startTimeMillis);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherImpl.java
index 781521c..063ca70 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherImpl.java
@@ -54,8 +54,8 @@
     }
 
     @Override
-    public void reportEvent(@CachedImageFetcherEvent int eventId) {
-        mCachedImageFetcherBridge.reportEvent(eventId);
+    public void reportEvent(String clientName, @CachedImageFetcherEvent int eventId) {
+        mCachedImageFetcherBridge.reportEvent(clientName, eventId);
     }
 
     @Override
@@ -64,13 +64,14 @@
     }
 
     @Override
-    public void fetchImage(String url, int width, int height, Callback<Bitmap> callback) {
-        fetchImageImpl(url, width, height, callback);
+    public void fetchImage(
+            String url, String clientName, int width, int height, Callback<Bitmap> callback) {
+        fetchImageImpl(url, clientName, width, height, callback);
     }
 
     @Override
-    public void fetchImage(String url, Callback<Bitmap> callback) {
-        fetchImageImpl(url, 0, 0, callback);
+    public void fetchImage(String url, String clientName, Callback<Bitmap> callback) {
+        fetchImageImpl(url, clientName, 0, 0, callback);
     }
 
     /**
@@ -83,7 +84,8 @@
      * @param callback The function which will be called when the image is ready.
      */
     @VisibleForTesting
-    void fetchImageImpl(String url, int width, int height, Callback<Bitmap> callback) {
+    void fetchImageImpl(
+            String url, String clientName, int width, int height, Callback<Bitmap> callback) {
         long startTimeMillis = System.currentTimeMillis();
         String filePath = mCachedImageFetcherBridge.getFilePath(url);
         new AsyncTask<Bitmap>() {
@@ -96,10 +98,10 @@
             protected void onPostExecute(Bitmap bitmap) {
                 if (bitmap != null) {
                     callback.onResult(bitmap);
-                    reportEvent(CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT);
-                    mCachedImageFetcherBridge.reportCacheHitTime(startTimeMillis);
+                    reportEvent(clientName, CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT);
+                    mCachedImageFetcherBridge.reportCacheHitTime(clientName, startTimeMillis);
                 } else {
-                    mCachedImageFetcherBridge.fetchImage(url, width, height, callback);
+                    mCachedImageFetcherBridge.fetchImage(url, clientName, callback);
                 }
             }
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/InMemoryCachedImageFetcher.java b/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/InMemoryCachedImageFetcher.java
index bd29fcb..84806b0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/InMemoryCachedImageFetcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/cached_image_fetcher/InMemoryCachedImageFetcher.java
@@ -48,8 +48,8 @@
     }
 
     @Override
-    public void reportEvent(@CachedImageFetcherEvent int eventId) {
-        mCachedImageFetcher.reportEvent(eventId);
+    public void reportEvent(String clientName, @CachedImageFetcherEvent int eventId) {
+        mCachedImageFetcher.reportEvent(clientName, eventId);
     }
 
     @Override
@@ -62,7 +62,8 @@
     }
 
     @Override
-    public void fetchImage(String url, int width, int height, Callback<Bitmap> callback) {
+    public void fetchImage(
+            String url, String clientName, int width, int height, Callback<Bitmap> callback) {
         Bitmap cachedBitmap = tryToGetBitmap(url, width, height);
         if (cachedBitmap == null) {
             if (mCachedImageFetcher == null) {
@@ -70,20 +71,21 @@
                 return;
             }
 
-            mCachedImageFetcher.fetchImage(url, width, height, (@Nullable Bitmap bitmap) -> {
-                bitmap = tryToResizeImage(bitmap, width, height);
-                storeBitmap(bitmap, url, width, height);
-                callback.onResult(bitmap);
-            });
+            mCachedImageFetcher.fetchImage(
+                    url, clientName, width, height, (@Nullable Bitmap bitmap) -> {
+                        bitmap = tryToResizeImage(bitmap, width, height);
+                        storeBitmap(bitmap, url, width, height);
+                        callback.onResult(bitmap);
+                    });
         } else {
-            reportEvent(CachedImageFetcherEvent.JAVA_IN_MEMORY_CACHE_HIT);
+            reportEvent(clientName, CachedImageFetcherEvent.JAVA_IN_MEMORY_CACHE_HIT);
             callback.onResult(cachedBitmap);
         }
     }
 
     @Override
-    public void fetchImage(String url, Callback<Bitmap> callback) {
-        fetchImage(url, 0, 0, callback);
+    public void fetchImage(String url, String clientName, Callback<Bitmap> callback) {
+        fetchImage(url, clientName, 0, 0, callback);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextual_suggestions/ContextualSuggestionsSourceImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/contextual_suggestions/ContextualSuggestionsSourceImpl.java
index febd0af..071d527 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextual_suggestions/ContextualSuggestionsSourceImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextual_suggestions/ContextualSuggestionsSourceImpl.java
@@ -22,6 +22,8 @@
  */
 class ContextualSuggestionsSourceImpl
         extends EmptySuggestionsSource implements ContextualSuggestionsSource {
+    private static final String CACHED_IMAGE_FETCHER_UMA_CLIENT_NAME = "ContextualSuggestions";
+
     private ContextualSuggestionsBridge mBridge;
     private CachedImageFetcher mCachedImageFetcher;
 
@@ -44,7 +46,7 @@
     @Override
     public void fetchSuggestionImage(SnippetArticle suggestion, Callback<Bitmap> callback) {
         String url = mBridge.getImageUrl(suggestion);
-        mCachedImageFetcher.fetchImage(url, callback);
+        mCachedImageFetcher.fetchImage(url, CACHED_IMAGE_FETCHER_UMA_CLIENT_NAME, callback);
     }
 
     @Override
@@ -56,7 +58,7 @@
             return;
         }
 
-        mCachedImageFetcher.fetchImage(url, callback);
+        mCachedImageFetcher.fetchImage(url, CACHED_IMAGE_FETCHER_UMA_CLIENT_NAME, callback);
     }
 
     @Override
@@ -68,7 +70,8 @@
             return;
         }
 
-        mCachedImageFetcher.fetchImage(url, desiredSizePx, desiredSizePx, callback);
+        mCachedImageFetcher.fetchImage(
+                url, CACHED_IMAGE_FETCHER_UMA_CLIENT_NAME, desiredSizePx, desiredSizePx, callback);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java
index e19cb19..0ce6425 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java
@@ -112,7 +112,8 @@
             if (activityTab != mTab) return;
 
             // Removes the hooks if the panel other than contextual search panel just got shown.
-            if (!getContextualSearchManager(mTab).isSearchPanelActive()) {
+            ContextualSearchManager manager = getContextualSearchManager(mTab);
+            if (manager != null && !manager.isSearchPanelActive()) {
                 mUnhookedTab = activityTab;
                 updateContextualSearchHooks(mUnhookedTab.getWebContents());
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarButtonInProductHelpController.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarButtonInProductHelpController.java
index f76272f2..38ad662 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarButtonInProductHelpController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarButtonInProductHelpController.java
@@ -174,6 +174,15 @@
                         accessibilityStringId, anchorView, appMenuHandler, activity));
     }
 
+    private static boolean shouldHighlightForIPH(String featureName) {
+        switch (featureName) {
+            case FeatureConstants.PREVIEWS_OMNIBOX_UI_FEATURE:
+                return false;
+            default:
+                return true;
+        }
+    }
+
     private static void maybeShowIPH(Tracker tracker, String featureName,
             Integer highlightMenuItemId, @StringRes int stringId,
             @StringRes int accessibilityStringId, View anchorView, AppMenuHandler appMenuHandler,
@@ -202,10 +211,14 @@
             textBubble.setDismissOnTouchInteraction(true);
             textBubble.addOnDismissListener(() -> anchorView.getHandler().postDelayed(() -> {
                 tracker.dismissed(featureName);
-                turnOffHighlightForTextBubble(appMenuHandler, anchorView);
+                if (shouldHighlightForIPH(featureName)) {
+                    turnOffHighlightForTextBubble(appMenuHandler, anchorView);
+                }
             }, ViewHighlighter.IPH_MIN_DELAY_BETWEEN_TWO_HIGHLIGHTS));
 
-            turnOnHighlightForTextBubble(appMenuHandler, highlightMenuItemId, anchorView);
+            if (shouldHighlightForIPH(featureName)) {
+                turnOnHighlightForTextBubble(appMenuHandler, highlightMenuItemId, anchorView);
+            }
 
             int yInsetPx = activity.getResources().getDimensionPixelOffset(
                     R.dimen.text_bubble_menu_anchor_y_inset);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/SilenceLintErrors.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/SilenceLintErrors.java
index 5718569..4ee6dc5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/SilenceLintErrors.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/SilenceLintErrors.java
@@ -10,12 +10,6 @@
 // we have moved resources into VR DFM.
 /* package */ class SilenceLintErrors {
     private int[] mRes = new int[] {
-            R.string.vr_shell_feedback_infobar_feedback_button,
-            R.string.vr_shell_feedback_infobar_description,
-            R.string.vr_services_check_infobar_install_text,
-            R.string.vr_services_check_infobar_update_text,
-            R.string.vr_services_check_infobar_install_button,
-            R.string.vr_services_check_infobar_update_button,
             R.anim.stay_hidden,
             R.drawable.vr_services,
     };
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
index f71fa2c..551a8ca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
@@ -828,8 +828,10 @@
 
         SimpleConfirmInfoBarBuilder.create(tab, listener,
                 InfoBarIdentifier.VR_FEEDBACK_INFOBAR_ANDROID, R.drawable.vr_services,
-                activity.getString(R.string.vr_shell_feedback_infobar_description),
-                activity.getString(R.string.vr_shell_feedback_infobar_feedback_button),
+                ContextUtils.getApplicationContext().getString(
+                        org.chromium.chrome.vr.R.string.vr_shell_feedback_infobar_description),
+                ContextUtils.getApplicationContext().getString(
+                        org.chromium.chrome.vr.R.string.vr_shell_feedback_infobar_feedback_button),
                 activity.getString(R.string.no_thanks), null /* linkText */,
                 true /* autoExpire  */);
     }
@@ -900,6 +902,7 @@
         mFeedbackFrequency = VrFeedbackStatus.getFeedbackFrequency();
         ensureLifecycleObserverInitialized();
         if (!mPaused) onResume();
+
         sInstance = this;
     }
 
@@ -982,13 +985,18 @@
 
         String infobarText;
         String buttonText;
+
         if (vrCoreCompatibility == VrCoreCompatibility.VR_NOT_AVAILABLE) {
             // Supported, but not installed. Ask user to install instead of upgrade.
-            infobarText = mActivity.getString(R.string.vr_services_check_infobar_install_text);
-            buttonText = mActivity.getString(R.string.vr_services_check_infobar_install_button);
+            infobarText = ContextUtils.getApplicationContext().getString(
+                    org.chromium.chrome.vr.R.string.vr_services_check_infobar_install_text);
+            buttonText = ContextUtils.getApplicationContext().getString(
+                    org.chromium.chrome.vr.R.string.vr_services_check_infobar_install_button);
         } else if (vrCoreCompatibility == VrCoreCompatibility.VR_OUT_OF_DATE) {
-            infobarText = mActivity.getString(R.string.vr_services_check_infobar_update_text);
-            buttonText = mActivity.getString(R.string.vr_services_check_infobar_update_button);
+            infobarText = ContextUtils.getApplicationContext().getString(
+                    org.chromium.chrome.vr.R.string.vr_services_check_infobar_update_text);
+            buttonText = ContextUtils.getApplicationContext().getString(
+                    org.chromium.chrome.vr.R.string.vr_services_check_infobar_update_button);
         } else {
             Log.e(TAG, "Unknown VrCore compatibility: " + vrCoreCompatibility);
             return;
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index 9441e8f..b89c9a4 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -3794,30 +3794,6 @@
         Webpage shortcuts
       </message>
 
-      <if expr="enable_vr">
-        <!-- Chrome VR feedback infobar -->
-        <message name="IDS_VR_SHELL_FEEDBACK_INFOBAR_DESCRIPTION" desc="Description for infobar prompting the user for feedback on their VR browsing experience.">
-          Help improve the VR experience in Chrome
-        </message>
-        <message name="IDS_VR_SHELL_FEEDBACK_INFOBAR_FEEDBACK_BUTTON" desc="Brief button text asking the user to provide feedback in the VR feedback infobar.">
-          Provide feedback
-        </message>
-
-        <!-- VR services check infobar -->
-        <message name="IDS_VR_SERVICES_CHECK_INFOBAR_INSTALL_TEXT" desc="Text to be displayed in the VR Services check infobar. When a WebVR page is loaded if the VR services that are needed to display WebVR don't exist an infobar will be shown to the user prompting them to install VR services.">
-          To view virtual reality content, install Google VR Services
-        </message>
-        <message name="IDS_VR_SERVICES_CHECK_INFOBAR_UPDATE_TEXT" desc="Text to be displayed in the VR Services check infobar. When a WebVR page is loaded if the VR services that are needed to display WebVR are out of date an infobar will be shown to the user prompting them to update VR services.">
-          To view virtual reality content, update Google VR Services
-        </message>
-        <message name="IDS_VR_SERVICES_CHECK_INFOBAR_INSTALL_BUTTON" desc="Text to be displayed in the VR Services check infobar confirm button for installing.">
-          Install
-        </message>
-        <message name="IDS_VR_SERVICES_CHECK_INFOBAR_UPDATE_BUTTON" desc="Text to be displayed in the VR Services check infobar confirm button for updating.">
-          Update
-        </message>
-      </if>
-
       <!-- VR module -->
       <message name="IDS_VR_MODULE_TITLE" desc="Text shown when the VR module is referenced in install start, success, failure UI (e.g. in IDS_MODULE_INSTALL_START_TEXT, which will expand to 'Installing Virtual Reality for Chrome…').">
         Virtual Reality
diff --git a/chrome/android/java/strings/android_chrome_vr_strings.grd b/chrome/android/java/strings/android_chrome_vr_strings.grd
new file mode 100644
index 0000000..58f8182
--- /dev/null
+++ b/chrome/android/java/strings/android_chrome_vr_strings.grd
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- android_chrome_strings.grd contains strings for VR DFM of Chrome for Android. -->
+<grit current_release="1" latest_public_release="0" output_all_resource_defines="false">
+  <outputs>
+    <output filename="values-am/android_chrome_vr_strings.xml" lang="am" type="android" />
+    <output filename="values-ar/android_chrome_vr_strings.xml" lang="ar" type="android" />
+    <output filename="values-bg/android_chrome_vr_strings.xml" lang="bg" type="android" />
+    <output filename="values-ca/android_chrome_vr_strings.xml" lang="ca" type="android" />
+    <output filename="values-cs/android_chrome_vr_strings.xml" lang="cs" type="android" />
+    <output filename="values-da/android_chrome_vr_strings.xml" lang="da" type="android" />
+    <output filename="values-de/android_chrome_vr_strings.xml" lang="de" type="android" />
+    <output filename="values-el/android_chrome_vr_strings.xml" lang="el" type="android" />
+    <output filename="values/android_chrome_vr_strings.xml" lang="en" type="android" />
+    <output filename="values-en-rGB/android_chrome_vr_strings.xml" lang="en-GB" type="android" />
+    <output filename="values-es/android_chrome_vr_strings.xml" lang="es" type="android" />
+    <output filename="values-es-rUS/android_chrome_vr_strings.xml" lang="es-419" type="android" />
+    <output filename="values-fa/android_chrome_vr_strings.xml" lang="fa" type="android" />
+    <output filename="values-fi/android_chrome_vr_strings.xml" lang="fi" type="android" />
+    <output filename="values-tl/android_chrome_vr_strings.xml" lang="fil" type="android" />
+    <output filename="values-fr/android_chrome_vr_strings.xml" lang="fr" type="android" />
+    <output filename="values-hi/android_chrome_vr_strings.xml" lang="hi" type="android" />
+    <output filename="values-hr/android_chrome_vr_strings.xml" lang="hr" type="android" />
+    <output filename="values-hu/android_chrome_vr_strings.xml" lang="hu" type="android" />
+    <output filename="values-in/android_chrome_vr_strings.xml" lang="id" type="android" />
+    <output filename="values-it/android_chrome_vr_strings.xml" lang="it" type="android" />
+    <output filename="values-iw/android_chrome_vr_strings.xml" lang="iw" type="android" />
+    <output filename="values-ja/android_chrome_vr_strings.xml" lang="ja" type="android" />
+    <output filename="values-ko/android_chrome_vr_strings.xml" lang="ko" type="android" />
+    <output filename="values-lt/android_chrome_vr_strings.xml" lang="lt" type="android" />
+    <output filename="values-lv/android_chrome_vr_strings.xml" lang="lv" type="android" />
+    <output filename="values-nl/android_chrome_vr_strings.xml" lang="nl" type="android" />
+    <output filename="values-nb/android_chrome_vr_strings.xml" lang="no" type="android" />
+    <output filename="values-pl/android_chrome_vr_strings.xml" lang="pl" type="android" />
+    <output filename="values-pt-rBR/android_chrome_vr_strings.xml" lang="pt-BR" type="android" />
+    <output filename="values-pt-rPT/android_chrome_vr_strings.xml" lang="pt-PT" type="android" />
+    <output filename="values-ro/android_chrome_vr_strings.xml" lang="ro" type="android" />
+    <output filename="values-ru/android_chrome_vr_strings.xml" lang="ru" type="android" />
+    <output filename="values-sk/android_chrome_vr_strings.xml" lang="sk" type="android" />
+    <output filename="values-sl/android_chrome_vr_strings.xml" lang="sl" type="android" />
+    <output filename="values-sr/android_chrome_vr_strings.xml" lang="sr" type="android" />
+    <output filename="values-sv/android_chrome_vr_strings.xml" lang="sv" type="android" />
+    <output filename="values-sw/android_chrome_vr_strings.xml" lang="sw" type="android" />
+    <output filename="values-th/android_chrome_vr_strings.xml" lang="th" type="android" />
+    <output filename="values-tr/android_chrome_vr_strings.xml" lang="tr" type="android" />
+    <output filename="values-uk/android_chrome_vr_strings.xml" lang="uk" type="android" />
+    <output filename="values-vi/android_chrome_vr_strings.xml" lang="vi" type="android" />
+    <output filename="values-zh-rCN/android_chrome_vr_strings.xml" lang="zh-CN" type="android" />
+    <output filename="values-zh-rTW/android_chrome_vr_strings.xml" lang="zh-TW" type="android" />
+  </outputs>
+  <translations>
+    <file lang="am" path="vr_translations/android_chrome_vr_strings_am.xtb" />
+    <file lang="ar" path="vr_translations/android_chrome_vr_strings_ar.xtb" />
+    <file lang="bg" path="vr_translations/android_chrome_vr_strings_bg.xtb" />
+    <file lang="bn" path="vr_translations/android_chrome_vr_strings_bn.xtb" />
+    <file lang="ca" path="vr_translations/android_chrome_vr_strings_ca.xtb" />
+    <file lang="cs" path="vr_translations/android_chrome_vr_strings_cs.xtb" />
+    <file lang="da" path="vr_translations/android_chrome_vr_strings_da.xtb" />
+    <file lang="de" path="vr_translations/android_chrome_vr_strings_de.xtb" />
+    <file lang="el" path="vr_translations/android_chrome_vr_strings_el.xtb" />
+    <file lang="en-GB" path="vr_translations/android_chrome_vr_strings_en-GB.xtb" />
+    <file lang="es" path="vr_translations/android_chrome_vr_strings_es.xtb" />
+    <file lang="es-419" path="vr_translations/android_chrome_vr_strings_es-419.xtb" />
+    <file lang="et" path="vr_translations/android_chrome_vr_strings_et.xtb" />
+    <file lang="fa" path="vr_translations/android_chrome_vr_strings_fa.xtb" />
+    <file lang="fi" path="vr_translations/android_chrome_vr_strings_fi.xtb" />
+    <file lang="fil" path="vr_translations/android_chrome_vr_strings_fil.xtb" />
+    <file lang="fr" path="vr_translations/android_chrome_vr_strings_fr.xtb" />
+    <file lang="gu" path="vr_translations/android_chrome_vr_strings_gu.xtb" />
+    <file lang="hi" path="vr_translations/android_chrome_vr_strings_hi.xtb" />
+    <file lang="hr" path="vr_translations/android_chrome_vr_strings_hr.xtb" />
+    <file lang="hu" path="vr_translations/android_chrome_vr_strings_hu.xtb" />
+    <file lang="id" path="vr_translations/android_chrome_vr_strings_id.xtb" />
+    <file lang="it" path="vr_translations/android_chrome_vr_strings_it.xtb" />
+    <file lang="iw" path="vr_translations/android_chrome_vr_strings_iw.xtb" />
+    <file lang="ja" path="vr_translations/android_chrome_vr_strings_ja.xtb" />
+    <file lang="ko" path="vr_translations/android_chrome_vr_strings_ko.xtb" />
+    <file lang="kn" path="vr_translations/android_chrome_vr_strings_kn.xtb" />
+    <file lang="lt" path="vr_translations/android_chrome_vr_strings_lt.xtb" />
+    <file lang="lv" path="vr_translations/android_chrome_vr_strings_lv.xtb" />
+    <file lang="ml" path="vr_translations/android_chrome_vr_strings_ml.xtb" />
+    <file lang="mr" path="vr_translations/android_chrome_vr_strings_mr.xtb" />
+    <file lang="ms" path="vr_translations/android_chrome_vr_strings_ms.xtb" />
+    <file lang="nl" path="vr_translations/android_chrome_vr_strings_nl.xtb" />
+    <file lang="no" path="vr_translations/android_chrome_vr_strings_no.xtb" />
+    <file lang="pl" path="vr_translations/android_chrome_vr_strings_pl.xtb" />
+    <file lang="pt-BR" path="vr_translations/android_chrome_vr_strings_pt-BR.xtb" />
+    <file lang="pt-PT" path="vr_translations/android_chrome_vr_strings_pt-PT.xtb" />
+    <file lang="ro" path="vr_translations/android_chrome_vr_strings_ro.xtb" />
+    <file lang="ru" path="vr_translations/android_chrome_vr_strings_ru.xtb" />
+    <file lang="sk" path="vr_translations/android_chrome_vr_strings_sk.xtb" />
+    <file lang="sl" path="vr_translations/android_chrome_vr_strings_sl.xtb" />
+    <file lang="sr" path="vr_translations/android_chrome_vr_strings_sr.xtb" />
+    <file lang="sv" path="vr_translations/android_chrome_vr_strings_sv.xtb" />
+    <file lang="sw" path="vr_translations/android_chrome_vr_strings_sw.xtb" />
+    <file lang="ta" path="vr_translations/android_chrome_vr_strings_ta.xtb" />
+    <file lang="te" path="vr_translations/android_chrome_vr_strings_te.xtb" />
+    <file lang="th" path="vr_translations/android_chrome_vr_strings_th.xtb" />
+    <file lang="tr" path="vr_translations/android_chrome_vr_strings_tr.xtb" />
+    <file lang="uk" path="vr_translations/android_chrome_vr_strings_uk.xtb" />
+    <file lang="vi" path="vr_translations/android_chrome_vr_strings_vi.xtb" />
+    <file lang="zh-CN" path="vr_translations/android_chrome_vr_strings_zh-CN.xtb" />
+    <file lang="zh-TW" path="vr_translations/android_chrome_vr_strings_zh-TW.xtb" />
+  </translations>
+  <release allow_pseudo="false" seq="1">
+    <messages fallback_to_english="true">
+        <!-- Chrome VR feedback infobar -->
+        <message name="IDS_VR_SHELL_FEEDBACK_INFOBAR_DESCRIPTION" desc="Description for infobar prompting the user for feedback on their VR browsing experience.">
+          Help improve the VR experience in Chrome
+        </message>
+        <message name="IDS_VR_SHELL_FEEDBACK_INFOBAR_FEEDBACK_BUTTON" desc="Brief button text asking the user to provide feedback in the VR feedback infobar.">
+          Provide feedback
+        </message>
+
+        <!-- VR services check infobar -->
+        <message name="IDS_VR_SERVICES_CHECK_INFOBAR_INSTALL_TEXT" desc="Text to be displayed in the VR Services check infobar. When a WebVR page is loaded if the VR services that are needed to display WebVR don't exist an infobar will be shown to the user prompting them to install VR services.">
+          To view virtual reality content, install Google VR Services
+        </message>
+        <message name="IDS_VR_SERVICES_CHECK_INFOBAR_UPDATE_TEXT" desc="Text to be displayed in the VR Services check infobar. When a WebVR page is loaded if the VR services that are needed to display WebVR are out of date an infobar will be shown to the user prompting them to update VR services.">
+          To view virtual reality content, update Google VR Services
+        </message>
+        <message name="IDS_VR_SERVICES_CHECK_INFOBAR_INSTALL_BUTTON" desc="Text to be displayed in the VR Services check infobar confirm button for installing.">
+          Install
+        </message>
+        <message name="IDS_VR_SERVICES_CHECK_INFOBAR_UPDATE_BUTTON" desc="Text to be displayed in the VR Services check infobar confirm button for updating.">
+          Update
+        </message>
+    </messages>
+  </release>
+</grit>
+
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_am.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_am.xtb
new file mode 100644
index 0000000..1a8356c
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_am.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="am"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ar.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ar.xtb
new file mode 100644
index 0000000..577c15f
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ar.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="ar"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_bg.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_bg.xtb
new file mode 100644
index 0000000..25d06b3
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_bg.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="bg"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_bn.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_bn.xtb
new file mode 100644
index 0000000..b02b1e7
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_bn.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="bn"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ca.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ca.xtb
new file mode 100644
index 0000000..1e9d243
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ca.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="ca"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_cs.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_cs.xtb
new file mode 100644
index 0000000..b6103f7
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_cs.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="cs"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_da.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_da.xtb
new file mode 100644
index 0000000..6feffd3
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_da.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="da"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_de.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_de.xtb
new file mode 100644
index 0000000..d2908e8a
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_de.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="de"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_el.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_el.xtb
new file mode 100644
index 0000000..2d96e6c
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_el.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="el"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_en-GB.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_en-GB.xtb
new file mode 100644
index 0000000..769a524
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_en-GB.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="en-GB"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_es-419.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_es-419.xtb
new file mode 100644
index 0000000..37258dd
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_es-419.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="es-419"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_es.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_es.xtb
new file mode 100644
index 0000000..27d8ca3
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_es.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="es"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_et.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_et.xtb
new file mode 100644
index 0000000..a14139f
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_et.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="et"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_fa.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_fa.xtb
new file mode 100644
index 0000000..41bc8c3
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_fa.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="fa"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_fi.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_fi.xtb
new file mode 100644
index 0000000..b2ed2bf
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_fi.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="fi"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_fil.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_fil.xtb
new file mode 100644
index 0000000..6ca5654
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_fil.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="fil"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_fr.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_fr.xtb
new file mode 100644
index 0000000..1ce4293
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_fr.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="fr"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_gu.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_gu.xtb
new file mode 100644
index 0000000..1b8a058
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_gu.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="gu"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_hi.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_hi.xtb
new file mode 100644
index 0000000..e9f9cc5
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_hi.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="hi"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_hr.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_hr.xtb
new file mode 100644
index 0000000..abb8268
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_hr.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="hr"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_hu.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_hu.xtb
new file mode 100644
index 0000000..5a7e2c9
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_hu.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="hu"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_id.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_id.xtb
new file mode 100644
index 0000000..bced312
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_id.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="id"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_it.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_it.xtb
new file mode 100644
index 0000000..d56be5c
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_it.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="it"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_iw.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_iw.xtb
new file mode 100644
index 0000000..d17d24e
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_iw.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="iw"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ja.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ja.xtb
new file mode 100644
index 0000000..c5828bf
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ja.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="ja"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_kn.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_kn.xtb
new file mode 100644
index 0000000..6f2561a
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_kn.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="kn"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ko.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ko.xtb
new file mode 100644
index 0000000..aac09f4
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ko.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="ko"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_lt.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_lt.xtb
new file mode 100644
index 0000000..e386c81
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_lt.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="lt"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_lv.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_lv.xtb
new file mode 100644
index 0000000..c27c406
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_lv.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="lv"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ml.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ml.xtb
new file mode 100644
index 0000000..970244e
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ml.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="ml"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_mr.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_mr.xtb
new file mode 100644
index 0000000..7eb198d
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_mr.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="mr"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ms.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ms.xtb
new file mode 100644
index 0000000..b8f88eb
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ms.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="ms"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_nl.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_nl.xtb
new file mode 100644
index 0000000..08c2024
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_nl.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="nl"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_no.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_no.xtb
new file mode 100644
index 0000000..52b6011
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_no.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="no"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_pl.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_pl.xtb
new file mode 100644
index 0000000..57c76f6
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_pl.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="pl"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_pt-BR.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_pt-BR.xtb
new file mode 100644
index 0000000..1ccc1be
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_pt-BR.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="pt-BR"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_pt-PT.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_pt-PT.xtb
new file mode 100644
index 0000000..448ac9de
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_pt-PT.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="pt-PT"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ro.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ro.xtb
new file mode 100644
index 0000000..ee107e4
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ro.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="ro"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ru.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ru.xtb
new file mode 100644
index 0000000..1161eea
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ru.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="ru"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sk.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sk.xtb
new file mode 100644
index 0000000..285c7cb
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sk.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="sk"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sl.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sl.xtb
new file mode 100644
index 0000000..5b94368
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sl.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="sl"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sr.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sr.xtb
new file mode 100644
index 0000000..037a5c14
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sr.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="sr"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sv.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sv.xtb
new file mode 100644
index 0000000..8f4581f
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sv.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="sv"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sw.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sw.xtb
new file mode 100644
index 0000000..0b25c33
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_sw.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="sw"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ta.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ta.xtb
new file mode 100644
index 0000000..ab9e8aa
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_ta.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="ta"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_te.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_te.xtb
new file mode 100644
index 0000000..c35f476
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_te.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="te"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_th.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_th.xtb
new file mode 100644
index 0000000..5d81291
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_th.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="th"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_tr.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_tr.xtb
new file mode 100644
index 0000000..ead1d39
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_tr.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="tr"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_uk.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_uk.xtb
new file mode 100644
index 0000000..29134e1
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_uk.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="uk"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_vi.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_vi.xtb
new file mode 100644
index 0000000..d858f99
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_vi.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="vi"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_zh-CN.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_zh-CN.xtb
new file mode 100644
index 0000000..effe01e
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_zh-CN.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="zh-CN"></translationbundle>
diff --git a/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_zh-TW.xtb b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_zh-TW.xtb
new file mode 100644
index 0000000..cb82bea
--- /dev/null
+++ b/chrome/android/java/strings/vr_translations/android_chrome_vr_strings_zh-TW.xtb
@@ -0,0 +1 @@
+<?xml version="1.0" ?><!DOCTYPE translationbundle><translationbundle lang="zh-TW"></translationbundle>
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 6b60827..f25bc7f 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
@@ -73,6 +73,7 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
@@ -1321,6 +1322,7 @@
     @Test
     @SmallTest
     @RetryOnFailure
+    @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP) // Details in https://crbug.com/709681.
     public void testPageLoadMetricsAreSent() throws Exception {
         checkPageLoadMetrics(true);
     }
@@ -1623,6 +1625,7 @@
     @Test
     @SmallTest
     @RetryOnFailure
+    @MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP) // Details in https://crbug.com/709681.
     public void testPostMessageReceivedFromPageWithLateRequest() throws Exception {
         final CallbackHelper messageChannelHelper = new CallbackHelper();
         final CallbackHelper onPostMessageHelper = new CallbackHelper();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrInstallUpdateInfoBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrInstallUpdateInfoBarTest.java
index 21d1fad..d0d4468 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrInstallUpdateInfoBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrInstallUpdateInfoBarTest.java
@@ -21,6 +21,7 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.test.params.ParameterAnnotations.ClassParameter;
 import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
 import org.chromium.base.test.params.ParameterSet;
@@ -83,15 +84,15 @@
             // Out of date and missing cases are the same, but with different text
             String expectedMessage, expectedButton;
             if (checkerReturnCompatibility == VrCoreCompatibility.VR_OUT_OF_DATE) {
-                expectedMessage = mVrTestRule.getActivity().getString(
-                        R.string.vr_services_check_infobar_update_text);
-                expectedButton = mVrTestRule.getActivity().getString(
-                        R.string.vr_services_check_infobar_update_button);
+                expectedMessage = ContextUtils.getApplicationContext().getString(
+                        org.chromium.chrome.vr.R.string.vr_services_check_infobar_update_text);
+                expectedButton = ContextUtils.getApplicationContext().getString(
+                        org.chromium.chrome.vr.R.string.vr_services_check_infobar_update_button);
             } else {
-                expectedMessage = mVrTestRule.getActivity().getString(
-                        R.string.vr_services_check_infobar_install_text);
-                expectedButton = mVrTestRule.getActivity().getString(
-                        R.string.vr_services_check_infobar_install_button);
+                expectedMessage = ContextUtils.getApplicationContext().getString(
+                        org.chromium.chrome.vr.R.string.vr_services_check_infobar_install_text);
+                expectedButton = ContextUtils.getApplicationContext().getString(
+                        org.chromium.chrome.vr.R.string.vr_services_check_infobar_install_button);
             }
             VrInfoBarUtils.expectInfoBarPresent(mVrTestRule, true);
             TextView tempView = (TextView) decorView.findViewById(R.id.infobar_message);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherImplTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherImplTest.java
index 0b4b7e9..45285a5 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherImplTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/cached_image_fetcher/CachedImageFetcherImplTest.java
@@ -6,7 +6,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.ArgumentMatchers.eq;
@@ -41,6 +40,7 @@
 @Config(manifest = Config.NONE,
         shadows = {ShadowUrlUtilities.class, BackgroundShadowAsyncTask.class})
 public class CachedImageFetcherImplTest {
+    private static final String UMA_CLIENT_NAME = "TestUmaClient";
     private static final String URL = "http://foo.bar";
     private static final int WIDTH_PX = 100;
     private static final int HEIGHT_PX = 200;
@@ -53,10 +53,6 @@
     Bitmap mBitmap;
 
     @Captor
-    ArgumentCaptor<Integer> mWidthCaptor;
-    @Captor
-    ArgumentCaptor<Integer> mHeightCaptor;
-    @Captor
     ArgumentCaptor<Callback<Bitmap>> mCallbackCaptor;
 
     @Before
@@ -69,63 +65,87 @@
             return null;
         })
                 .when(mCachedImageFetcherBridge)
-                .fetchImage(eq(URL), mWidthCaptor.capture(), mHeightCaptor.capture(),
-                        mCallbackCaptor.capture());
+                .fetchImage(eq(URL), eq(UMA_CLIENT_NAME), mCallbackCaptor.capture());
     }
 
     @Test
     @SmallTest
     public void testFetchImageWithDimensionsFoundOnDisk() throws Exception {
         Mockito.doReturn(mBitmap).when(mCachedImageFetcher).tryToLoadImageFromDisk(anyObject());
-        mCachedImageFetcher.fetchImage(
-                URL, WIDTH_PX, HEIGHT_PX, (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
+        mCachedImageFetcher.fetchImage(URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX,
+                (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
         BackgroundShadowAsyncTask.runBackgroundTasks();
         ShadowLooper.runUiThreadTasks();
 
-        verify(mCachedImageFetcher).fetchImageImpl(eq(URL), eq(WIDTH_PX), eq(HEIGHT_PX), any());
+        verify(mCachedImageFetcher)
+                .fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(WIDTH_PX), eq(HEIGHT_PX), any());
         verify(mCachedImageFetcherBridge, never()) // Should never make it to native.
-                .fetchImage(eq(URL), anyInt(), anyInt(), any());
+                .fetchImage(eq(URL), eq(UMA_CLIENT_NAME), any());
 
         // Verify metrics have been reported.
         verify(mCachedImageFetcherBridge)
-                .reportEvent(eq(CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT));
-        verify(mCachedImageFetcherBridge).reportCacheHitTime(anyLong());
+                .reportEvent(eq(UMA_CLIENT_NAME), eq(CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT));
+        verify(mCachedImageFetcherBridge).reportCacheHitTime(eq(UMA_CLIENT_NAME), anyLong());
     }
 
     @Test
     @SmallTest
     public void testFetchImageWithDimensionsCallToNative() throws Exception {
         Mockito.doReturn(null).when(mCachedImageFetcher).tryToLoadImageFromDisk(anyObject());
-        mCachedImageFetcher.fetchImage(
-                URL, WIDTH_PX, HEIGHT_PX, (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
+        mCachedImageFetcher.fetchImage(URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX,
+                (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
         BackgroundShadowAsyncTask.runBackgroundTasks();
         ShadowLooper.runUiThreadTasks();
 
-        verify(mCachedImageFetcher).fetchImageImpl(eq(URL), eq(WIDTH_PX), eq(HEIGHT_PX), any());
-        verify(mCachedImageFetcherBridge).fetchImage(eq(URL), anyInt(), anyInt(), any());
+        verify(mCachedImageFetcher)
+                .fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(WIDTH_PX), eq(HEIGHT_PX), any());
+        verify(mCachedImageFetcherBridge).fetchImage(eq(URL), eq(UMA_CLIENT_NAME), any());
     }
 
     @Test
     @SmallTest
     public void testFetchImageWithNoDimensionsFoundOnDisk() throws Exception {
         Mockito.doReturn(mBitmap).when(mCachedImageFetcher).tryToLoadImageFromDisk(anyObject());
-        mCachedImageFetcher.fetchImage(URL, (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
+        mCachedImageFetcher.fetchImage(
+                URL, UMA_CLIENT_NAME, (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
         BackgroundShadowAsyncTask.runBackgroundTasks();
         ShadowLooper.runUiThreadTasks();
 
-        verify(mCachedImageFetcher).fetchImageImpl(eq(URL), eq(0), eq(0), any());
-        verify(mCachedImageFetcherBridge, never()).fetchImage(eq(URL), anyInt(), anyInt(), any());
+        verify(mCachedImageFetcher)
+                .fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(0), eq(0), any());
+        verify(mCachedImageFetcherBridge, never()).fetchImage(eq(URL), eq(UMA_CLIENT_NAME), any());
     }
 
     @Test
     @SmallTest
     public void testFetchImageWithNoDimensionsCallToNative() throws Exception {
         Mockito.doReturn(null).when(mCachedImageFetcher).tryToLoadImageFromDisk(anyObject());
-        mCachedImageFetcher.fetchImage(URL, (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
+        mCachedImageFetcher.fetchImage(
+                URL, UMA_CLIENT_NAME, (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
         BackgroundShadowAsyncTask.runBackgroundTasks();
         ShadowLooper.runUiThreadTasks();
 
-        verify(mCachedImageFetcher).fetchImageImpl(eq(URL), eq(0), eq(0), any());
-        verify(mCachedImageFetcherBridge).fetchImage(eq(URL), anyInt(), anyInt(), any());
+        verify(mCachedImageFetcher)
+                .fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(0), eq(0), any());
+        verify(mCachedImageFetcherBridge).fetchImage(eq(URL), eq(UMA_CLIENT_NAME), any());
     }
-}
\ No newline at end of file
+
+    @Test
+    @SmallTest
+    public void testFetchTwoClients() throws Exception {
+        Mockito.doReturn(null).when(mCachedImageFetcher).tryToLoadImageFromDisk(anyObject());
+        mCachedImageFetcher.fetchImage(
+                URL, UMA_CLIENT_NAME, (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
+        mCachedImageFetcher.fetchImage(
+                URL, UMA_CLIENT_NAME + "2", (Bitmap bitmap) -> { assertEquals(bitmap, mBitmap); });
+        BackgroundShadowAsyncTask.runBackgroundTasks();
+        ShadowLooper.runUiThreadTasks();
+
+        verify(mCachedImageFetcher)
+                .fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(0), eq(0), any());
+        verify(mCachedImageFetcher)
+                .fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME + "2"), eq(0), eq(0), any());
+        verify(mCachedImageFetcherBridge).fetchImage(eq(URL), eq(UMA_CLIENT_NAME), any());
+        verify(mCachedImageFetcherBridge).fetchImage(eq(URL), eq(UMA_CLIENT_NAME + "2"), any());
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/cached_image_fetcher/InMemoryCachedImageFetcherTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/cached_image_fetcher/InMemoryCachedImageFetcherTest.java
index 496adbf..2734ce1 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/cached_image_fetcher/InMemoryCachedImageFetcherTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/cached_image_fetcher/InMemoryCachedImageFetcherTest.java
@@ -41,6 +41,7 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class InMemoryCachedImageFetcherTest {
+    private static final String UMA_CLIENT_NAME = "TestUmaClient";
     private static final String URL = "http://foo.bar";
     private static final int WIDTH_PX = 100;
     private static final int HEIGHT_PX = 200;
@@ -88,8 +89,8 @@
             mCallbackCaptor.getValue().onResult(bitmap);
             return null;
         }).when(mCachedImageFetcherImpl)
-                .fetchImage(eq(URL), mWidthCaptor.capture(), mHeightCaptor.capture(),
-                        mCallbackCaptor.capture());
+                .fetchImage(eq(URL), eq(UMA_CLIENT_NAME), mWidthCaptor.capture(),
+                        mHeightCaptor.capture(), mCallbackCaptor.capture());
         // clang-format on
 
         doReturn(bitmap)
@@ -101,19 +102,22 @@
     @SmallTest
     public void testFetchImageCachesFirstCall() throws Exception {
         answerFetch(mBitmap, mCachedImageFetcherImpl, false);
-        mInMemoryCachedImageFetcher.fetchImage(URL, WIDTH_PX, HEIGHT_PX, mCallback);
+        mInMemoryCachedImageFetcher.fetchImage(
+                URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX, mCallback);
         verify(mCallback).onResult(eq(mBitmap));
 
         reset(mCallback);
-        mInMemoryCachedImageFetcher.fetchImage(URL, WIDTH_PX, HEIGHT_PX, mCallback);
+        mInMemoryCachedImageFetcher.fetchImage(
+                URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX, mCallback);
         verify(mCallback).onResult(eq(mBitmap));
 
         verify(mCachedImageFetcherImpl, /* Should only go to native the first time. */ times(1))
-                .fetchImage(eq(URL), eq(WIDTH_PX), eq(HEIGHT_PX), any());
+                .fetchImage(eq(URL), eq(UMA_CLIENT_NAME), eq(WIDTH_PX), eq(HEIGHT_PX), any());
 
         // Verify metrics are reported.
         verify(mCachedImageFetcherImpl)
-                .reportEvent(CachedImageFetcherEvent.JAVA_IN_MEMORY_CACHE_HIT);
+                .reportEvent(
+                        eq(UMA_CLIENT_NAME), eq(CachedImageFetcherEvent.JAVA_IN_MEMORY_CACHE_HIT));
     }
 
     @Test
@@ -124,11 +128,12 @@
                 .when(mInMemoryCachedImageFetcher)
                 .tryToGetBitmap(eq(URL), eq(WIDTH_PX), eq(HEIGHT_PX));
 
-        mInMemoryCachedImageFetcher.fetchImage(URL, WIDTH_PX, HEIGHT_PX, mCallback);
+        mInMemoryCachedImageFetcher.fetchImage(
+                URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX, mCallback);
         verify(mCallback).onResult(eq(null));
 
         verify(mCachedImageFetcherImpl, /* Shouldn't make the call at all. */ times(0))
-                .fetchImage(eq(URL), eq(WIDTH_PX), eq(HEIGHT_PX), any());
+                .fetchImage(eq(URL), eq(UMA_CLIENT_NAME), eq(WIDTH_PX), eq(HEIGHT_PX), any());
     }
 
     @Test
@@ -172,9 +177,10 @@
             answerFetch(mBitmap, mCachedImageFetcherImpl, true);
 
             // No exception should be thrown here when bitmap cache is null.
-            mInMemoryCachedImageFetcher.fetchImage(URL, WIDTH_PX, HEIGHT_PX, (Bitmap bitmap) -> {});
+            mInMemoryCachedImageFetcher.fetchImage(
+                    URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX, (Bitmap bitmap) -> {});
         } catch (Exception e) {
             fail("Destroy called in the middle of execution shouldn't throw");
         }
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/contextual_suggestions/ContextualSuggestionsSourceImplTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/contextual_suggestions/ContextualSuggestionsSourceImplTest.java
index 6643969..8756032 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/contextual_suggestions/ContextualSuggestionsSourceImplTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/contextual_suggestions/ContextualSuggestionsSourceImplTest.java
@@ -66,7 +66,7 @@
             return null;
         })
                 .when(mCachedImageFetcher)
-                .fetchImage(any(), mCallbackCaptor.capture());
+                .fetchImage(any(), any(), mCallbackCaptor.capture());
     }
 
     @Test
diff --git a/chrome/android/modules/module_names_to_package_ids.gni b/chrome/android/modules/module_names_to_package_ids.gni
index 400ddf9..3ab538f 100644
--- a/chrome/android/modules/module_names_to_package_ids.gni
+++ b/chrome/android/modules/module_names_to_package_ids.gni
@@ -1,2 +1,5 @@
 # Mapping that controls package IDs assigned to modules.
-resource_packages_id_mapping = [ "ar=0x7e" ]
+resource_packages_id_mapping = [
+  "ar=0x7e",
+  "vr=0x7d",
+]
diff --git a/chrome/android/modules/vr/vr_module_tmpl.gni b/chrome/android/modules/vr/vr_module_tmpl.gni
index d99efdb..fcabfa9 100644
--- a/chrome/android/modules/vr/vr_module_tmpl.gni
+++ b/chrome/android/modules/vr/vr_module_tmpl.gni
@@ -5,6 +5,7 @@
 import("//base/android/linker/config.gni")
 import("//build/config/android/rules.gni")
 import("//build/config/locales.gni")
+import("//chrome/android/modules/module_names_to_package_ids.gni")
 import("//device/vr/buildflags/buildflags.gni")
 
 assert(enable_vr)
@@ -47,5 +48,8 @@
 
     # Don't embed more localized strings than required (http://crbug.com/932017)
     aapt_locale_whitelist = locales
+
+    package_name = "vr"
+    package_name_to_id_mapping = resource_packages_id_mapping
   }
 }
diff --git a/chrome/app/BUILD.gn b/chrome/app/BUILD.gn
index e059398..ec4bc38 100644
--- a/chrome/app/BUILD.gn
+++ b/chrome/app/BUILD.gn
@@ -390,10 +390,7 @@
   }
 
   if (enable_mus) {
-    deps += [
-      "//ash/components/quick_launch/public/mojom:constants",
-      "//services/ws/public/mojom:constants",
-    ]
+    deps += [ "//services/ws/public/mojom:constants" ]
   }
 
   if (enable_gwp_asan) {
@@ -606,7 +603,6 @@
 
   if (is_chromeos) {
     deps += [
-      "//ash/components/quick_launch/public/cpp:manifest",
       "//ash/components/shortcut_viewer/public/cpp:manifest",
       "//ash/components/tap_visualizer/public/cpp:manifest",
       "//ash/public/cpp:manifest",
@@ -614,7 +610,6 @@
       "//chrome/services/cups_ipp_parser/public/cpp:manifest",
       "//chromeos/services/ime/public/cpp:manifest",
       "//chromeos/services/secure_channel/public/cpp:manifest",
-      "//mash/public/mojom",
       "//services/ws/public/mojom/input_devices",
       "//ui/accessibility:ax_host_manifest",
     ]
diff --git a/chrome/app/DEPS b/chrome/app/DEPS
index 039bbff..fb87c9d 100644
--- a/chrome/app/DEPS
+++ b/chrome/app/DEPS
@@ -1,5 +1,4 @@
 include_rules = [
-  "+ash/components/quick_launch/public",
   "+ash/public",
   "+chrome/browser",
   "+chrome/child",
@@ -81,7 +80,6 @@
     "+third_party/blink/public/mojom",
   ],
   "chrome_packaged_service_manifests\.cc": [
-    "+ash/components/quick_launch/public",
     "+ash/components/shortcut_viewer/public",
     "+ash/components/tap_visualizer/public",
     "+chrome/services/cups_ipp_parser/public",
diff --git a/chrome/app/chrome_content_browser_overlay_manifest.cc b/chrome/app/chrome_content_browser_overlay_manifest.cc
index de1a1a6..e32ae57 100644
--- a/chrome/app/chrome_content_browser_overlay_manifest.cc
+++ b/chrome/app/chrome_content_browser_overlay_manifest.cc
@@ -96,7 +96,7 @@
 #if defined(OS_CHROMEOS)
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           ws::switches::kUseTestConfig)) {
-    manifest.required_capabilities.push_back({"ui", "test"});
+    manifest.required_capabilities["ui"].insert("test");
   }
 #endif
   return manifest;
diff --git a/chrome/app/chrome_packaged_service_manifests.cc b/chrome/app/chrome_packaged_service_manifests.cc
index b7b119a..91da8e9 100644
--- a/chrome/app/chrome_packaged_service_manifests.cc
+++ b/chrome/app/chrome_packaged_service_manifests.cc
@@ -22,7 +22,6 @@
 #include "services/service_manager/public/cpp/manifest_builder.h"
 
 #if defined(OS_CHROMEOS)
-#include "ash/components/quick_launch/public/cpp/manifest.h"
 #include "ash/components/shortcut_viewer/public/cpp/manifest.h"
 #include "ash/components/tap_visualizer/public/cpp/manifest.h"
 #include "ash/public/cpp/manifest.h"
@@ -30,7 +29,6 @@
 #include "chrome/services/cups_ipp_parser/public/cpp/manifest.h"
 #include "chromeos/services/ime/public/cpp/manifest.h"
 #include "chromeos/services/secure_channel/public/cpp/manifest.h"
-#include "mash/public/mojom/launchable.mojom.h"  // nogncheck
 #include "services/ws/public/mojom/input_devices/input_device_controller.mojom.h"
 #include "ui/accessibility/ax_host_manifest.h"  // nogncheck
 #endif
@@ -100,9 +98,6 @@
         .ExposeCapability("input_device_controller",
                           service_manager::Manifest::InterfaceList<
                               ws::mojom::InputDeviceController>())
-        .ExposeCapability(
-            "mash:launchable",
-            service_manager::Manifest::InterfaceList<mash::mojom::Launchable>())
 #endif
         .RequireCapability(chrome::mojom::kRendererServiceName, "browser")
         .Build()
@@ -169,7 +164,6 @@
       GetProfileImportManifest(),
 #endif
 #if defined(OS_CHROMEOS)
-      quick_launch::GetManifest(),
       shortcut_viewer::GetManifest(),
       tap_visualizer::GetManifest(),
       ash::GetManifest(),
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index fb92e82..b8db426 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -3683,9 +3683,6 @@
   <message name="IDS_ASH_ASH_SERVICE_NAME" desc="Name of the internal software service that manages windows. Appears in the task manager.">
     Window manager
   </message>
-  <message name="IDS_ASH_QUICK_LAUNCH_APP_NAME" desc="Name of the app used to launch other apps by typing an app name. Appears in the task manager.">
-    Quick launch
-  </message>
   <message name="IDS_ASH_SHORTCUT_VIEWER_APP_NAME" desc="Name of the app that shows the list of keyboard shortcuts. Appears in the task manager.">
     Shortcut viewer
   </message>
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index da171f6..94f3943 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3360,7 +3360,6 @@
       "upgrade_detector/upgrade_detector_chromeos.h",
     ]
     deps += [
-      "//ash/components/quick_launch/public/mojom:constants",
       "//ash/public/cpp",
       "//chrome/browser/chromeos",
       "//chromeos/services/assistant/public:feature_flags",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 12f830b..c23579d 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -110,7 +110,6 @@
   # interfaces in //ash/public/interfaces. See //ash/README.md.
   "-ash",
   "+ash/public",
-  "+ash/components/quick_launch/public/mojom",
   "+ash/components/shortcut_viewer/public/mojom",
   "+ash/components/tap_visualizer/public/mojom",
 
diff --git a/chrome/browser/android/cached_image_fetcher/cached_image_fetcher_bridge.cc b/chrome/browser/android/cached_image_fetcher/cached_image_fetcher_bridge.cc
index bf1524c..99b48a5 100644
--- a/chrome/browser/android/cached_image_fetcher/cached_image_fetcher_bridge.cc
+++ b/chrome/browser/android/cached_image_fetcher/cached_image_fetcher_bridge.cc
@@ -102,14 +102,14 @@
 void CachedImageFetcherBridge::FetchImage(JNIEnv* j_env,
                                           const JavaRef<jobject>& j_this,
                                           const JavaRef<jstring>& j_url,
-                                          const jint width_px,
-                                          const jint height_px,
+                                          const JavaRef<jstring>& j_client_name,
                                           const JavaRef<jobject>& j_callback) {
   ScopedJavaGlobalRef<jobject> callback(j_callback);
   std::string url = base::android::ConvertJavaStringToUTF8(j_url);
+  std::string client_name =
+      base::android::ConvertJavaStringToUTF8(j_client_name);
 
-  ImageFetcherParams params(kTrafficAnnotation);
-  params.set_frame_size(gfx::Size(width_px, height_px));
+  ImageFetcherParams params(kTrafficAnnotation, client_name);
 
   // TODO(wylieb): We checked disk in Java, so provide a way to tell
   // CachedImageFetcher to skip checking disk in native.
@@ -123,18 +123,25 @@
 void CachedImageFetcherBridge::ReportEvent(
     JNIEnv* j_env,
     const base::android::JavaRef<jobject>& j_this,
+    const base::android::JavaRef<jstring>& j_client_name,
     const jint j_event_id) {
+  std::string client_name =
+      base::android::ConvertJavaStringToUTF8(j_client_name);
   CachedImageFetcherEvent event =
       static_cast<CachedImageFetcherEvent>(j_event_id);
-  CachedImageFetcherMetricsReporter::ReportEvent(event);
+  CachedImageFetcherMetricsReporter::ReportEvent(client_name, event);
 }
 
 void CachedImageFetcherBridge::ReportCacheHitTime(
     JNIEnv* j_env,
     const base::android::JavaRef<jobject>& j_this,
+    const base::android::JavaRef<jstring>& j_client_name,
     const jlong start_time_millis) {
+  std::string client_name =
+      base::android::ConvertJavaStringToUTF8(j_client_name);
   base::Time start_time = base::Time::FromJavaTime(start_time_millis);
-  CachedImageFetcherMetricsReporter::ReportImageLoadFromCacheTime(start_time);
+  CachedImageFetcherMetricsReporter::ReportImageLoadFromCacheTimeJava(
+      client_name, start_time);
 }
 
 void CachedImageFetcherBridge::OnImageFetched(
diff --git a/chrome/browser/android/cached_image_fetcher/cached_image_fetcher_bridge.h b/chrome/browser/android/cached_image_fetcher/cached_image_fetcher_bridge.h
index 8869732..a976b9b 100644
--- a/chrome/browser/android/cached_image_fetcher/cached_image_fetcher_bridge.h
+++ b/chrome/browser/android/cached_image_fetcher/cached_image_fetcher_bridge.h
@@ -35,16 +35,17 @@
   void FetchImage(JNIEnv* j_env,
                   const base::android::JavaRef<jobject>& j_this,
                   const base::android::JavaRef<jstring>& j_url,
-                  const jint width_px,
-                  const jint height_px,
+                  const base::android::JavaRef<jstring>& j_client_name,
                   const base::android::JavaRef<jobject>& j_callback);
 
   void ReportEvent(JNIEnv* j_env,
                    const base::android::JavaRef<jobject>& j_this,
+                   const base::android::JavaRef<jstring>& j_client_name,
                    const jint j_event_id);
 
   void ReportCacheHitTime(JNIEnv* j_env,
                           const base::android::JavaRef<jobject>& j_this,
+                          const base::android::JavaRef<jstring>& j_client_name,
                           const jlong start_time_millis);
 
  private:
diff --git a/chrome/browser/android/explore_sites/explore_sites_bridge_experimental.cc b/chrome/browser/android/explore_sites/explore_sites_bridge_experimental.cc
index 9adb58b..462e687 100644
--- a/chrome/browser/android/explore_sites/explore_sites_bridge_experimental.cc
+++ b/chrome/browser/android/explore_sites/explore_sites_bridge_experimental.cc
@@ -38,6 +38,8 @@
 
 namespace {
 
+constexpr char kImageFetcherUmaClientName[] = "ExploreSitesExperimental";
+
 constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
     net::DefineNetworkTrafficAnnotation("explore_sites_image_fetcher", R"(
 semantics {
@@ -125,6 +127,9 @@
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
       content::BrowserContext::GetDefaultStoragePartition(profile)
           ->GetURLLoaderFactoryForBrowserProcess();
+  image_fetcher::ImageFetcherParams params(kTrafficAnnotation,
+                                           kImageFetcherUmaClientName);
+
   auto image_fetcher = std::make_unique<image_fetcher::ImageFetcherImpl>(
       std::make_unique<suggestions::ImageDecoderImpl>(), url_loader_factory);
   // |image_fetcher| will be owned by the callback and gets destroyed at the end
@@ -134,7 +139,7 @@
       icon_url,
       base::BindOnce(&OnGetIconDone, std::move(image_fetcher),
                      ScopedJavaGlobalRef<jobject>(j_callback_obj)),
-      kTrafficAnnotation);
+      std::move(params));
 }
 
 }  // namespace explore_sites
diff --git a/chrome/browser/android/vr/BUILD.gn b/chrome/browser/android/vr/BUILD.gn
index 64716f5..54c90d0 100644
--- a/chrome/browser/android/vr/BUILD.gn
+++ b/chrome/browser/android/vr/BUILD.gn
@@ -184,6 +184,10 @@
     "//ui/android:ui_full_java",
     "//ui/android:ui_utils_java",
   ]
+
+  deps = [
+    ":vr_java_resources",
+  ]
 }
 
 android_library("ar_java") {
@@ -199,6 +203,67 @@
   ]
 }
 
+android_resources("vr_java_resources") {
+  resource_dirs = []
+
+  deps = [
+    ":vr_strings_grd",
+  ]
+
+  custom_package = "org.chromium.chrome.vr"
+}
+
+java_strings_grd("vr_strings_grd") {
+  defines = chrome_grit_defines
+  grd_file = "//chrome/android/java/strings/android_chrome_vr_strings.grd"
+  outputs = [
+    "values-am/android_chrome_vr_strings.xml",
+    "values-ar/android_chrome_vr_strings.xml",
+    "values-bg/android_chrome_vr_strings.xml",
+    "values-ca/android_chrome_vr_strings.xml",
+    "values-cs/android_chrome_vr_strings.xml",
+    "values-da/android_chrome_vr_strings.xml",
+    "values-de/android_chrome_vr_strings.xml",
+    "values-el/android_chrome_vr_strings.xml",
+    "values/android_chrome_vr_strings.xml",
+    "values-en-rGB/android_chrome_vr_strings.xml",
+    "values-es/android_chrome_vr_strings.xml",
+    "values-es-rUS/android_chrome_vr_strings.xml",
+    "values-fa/android_chrome_vr_strings.xml",
+    "values-fi/android_chrome_vr_strings.xml",
+    "values-tl/android_chrome_vr_strings.xml",
+    "values-fr/android_chrome_vr_strings.xml",
+    "values-hi/android_chrome_vr_strings.xml",
+    "values-hr/android_chrome_vr_strings.xml",
+    "values-hu/android_chrome_vr_strings.xml",
+    "values-in/android_chrome_vr_strings.xml",
+    "values-it/android_chrome_vr_strings.xml",
+    "values-iw/android_chrome_vr_strings.xml",
+    "values-ja/android_chrome_vr_strings.xml",
+    "values-ko/android_chrome_vr_strings.xml",
+    "values-lt/android_chrome_vr_strings.xml",
+    "values-lv/android_chrome_vr_strings.xml",
+    "values-nl/android_chrome_vr_strings.xml",
+    "values-nb/android_chrome_vr_strings.xml",
+    "values-pl/android_chrome_vr_strings.xml",
+    "values-pt-rBR/android_chrome_vr_strings.xml",
+    "values-pt-rPT/android_chrome_vr_strings.xml",
+    "values-ro/android_chrome_vr_strings.xml",
+    "values-ru/android_chrome_vr_strings.xml",
+    "values-sk/android_chrome_vr_strings.xml",
+    "values-sl/android_chrome_vr_strings.xml",
+    "values-sr/android_chrome_vr_strings.xml",
+    "values-sv/android_chrome_vr_strings.xml",
+    "values-sw/android_chrome_vr_strings.xml",
+    "values-th/android_chrome_vr_strings.xml",
+    "values-tr/android_chrome_vr_strings.xml",
+    "values-uk/android_chrome_vr_strings.xml",
+    "values-vi/android_chrome_vr_strings.xml",
+    "values-zh-rCN/android_chrome_vr_strings.xml",
+    "values-zh-rTW/android_chrome_vr_strings.xml",
+  ]
+}
+
 generate_jni("vr_jni_headers") {
   sources = [
     "//chrome/android/java/src/org/chromium/chrome/browser/vr/AndroidUiGestureTarget.java",
diff --git a/chrome/browser/apps/app_service/extension_apps.cc b/chrome/browser/apps/app_service/extension_apps.cc
index 444b681..e6a28fe 100644
--- a/chrome/browser/apps/app_service/extension_apps.cc
+++ b/chrome/browser/apps/app_service/extension_apps.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ui/app_list/extension_app_utils.h"
+#include "chrome/browser/ui/app_list/extension_uninstaller.h"
 #include "chrome/browser/ui/app_list/search/search_util.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/chrome_pages.h"
@@ -226,29 +227,10 @@
     return;
   }
 
-  const extensions::Extension* extension =
-      extensions::ExtensionRegistry::Get(profile_)->GetInstalledExtension(
-          app_id);
-
-  if (!extension) {
-    return;
-  }
-
-  // TODO(crbug.com/826982): The UninstallReason should eventually be passed
-  // through from the subscriber. This might involve generalising uninstall
-  // reason into an App Service concept.
-  base::string16 error;
-  bool uninstalled =
-      extensions::ExtensionSystem::Get(profile_)
-          ->extension_service()
-          ->UninstallExtension(
-              app_id,
-              extensions::UninstallReason::UNINSTALL_REASON_USER_INITIATED,
-              &error);
-
-  if (!uninstalled) {
-    LOG(ERROR) << "Couldn't uninstall app with id " << app_id << ". " << error;
-  }
+  // ExtensionUninstaller deletes itself when done or aborted.
+  ExtensionUninstaller* uninstaller =
+      new ExtensionUninstaller(profile_, app_id);
+  uninstaller->Run();
 }
 
 void ExtensionApps::OpenNativeSettings(const std::string& app_id) {
diff --git a/chrome/browser/apps/platform_apps/app_browsertest.cc b/chrome/browser/apps/platform_apps/app_browsertest.cc
index 14db9cd..db83368 100644
--- a/chrome/browser/apps/platform_apps/app_browsertest.cc
+++ b/chrome/browser/apps/platform_apps/app_browsertest.cc
@@ -1420,7 +1420,10 @@
   ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
       web_contents, "exitPictureInPicture();", &result));
   EXPECT_TRUE(result);
-  EXPECT_FALSE(window_controller->GetWindowForTesting()->IsVisible());
+  // TODO(mlamouri): this check is relying on IPC timing. The timing will be
+  // fixed with
+  // https://chromium-review.googlesource.com/c/chromium/src/+/1409544
+  // EXPECT_FALSE(window_controller->GetWindowForTesting()->IsVisible());
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/ash_service_registry.cc b/chrome/browser/ash_service_registry.cc
index 0609649..b48ae74 100644
--- a/chrome/browser/ash_service_registry.cc
+++ b/chrome/browser/ash_service_registry.cc
@@ -5,7 +5,6 @@
 #include "chrome/browser/ash_service_registry.h"
 
 #include "ash/ash_service.h"
-#include "ash/components/quick_launch/public/mojom/constants.mojom.h"
 #include "ash/components/shortcut_viewer/public/mojom/shortcut_viewer.mojom.h"
 #include "ash/components/tap_visualizer/public/mojom/tap_visualizer.mojom.h"
 #include "ash/public/cpp/window_properties.h"
@@ -36,7 +35,6 @@
 
 // Services shared between mash and non-mash configs.
 constexpr Service kCommonServices[] = {
-    {quick_launch::mojom::kServiceName, IDS_ASH_QUICK_LAUNCH_APP_NAME},
     {shortcut_viewer::mojom::kServiceName, IDS_ASH_SHORTCUT_VIEWER_APP_NAME},
     {tap_visualizer::mojom::kServiceName, IDS_ASH_TAP_VISUALIZER_APP_NAME},
 };
diff --git a/chrome/browser/autofill/autofill_uitest_util.cc b/chrome/browser/autofill/autofill_uitest_util.cc
index eb51584..34b2b66 100644
--- a/chrome/browser/autofill/autofill_uitest_util.cc
+++ b/chrome/browser/autofill/autofill_uitest_util.cc
@@ -108,4 +108,9 @@
   observer.Wait();
 }
 
+void WaitForPersonalDataChange(Browser* browser) {
+  PdmChangeWaiter observer(browser);
+  observer.Wait();
+}
+
 }  // namespace autofill
diff --git a/chrome/browser/autofill/autofill_uitest_util.h b/chrome/browser/autofill/autofill_uitest_util.h
index 89ce6d15..df333ec 100644
--- a/chrome/browser/autofill/autofill_uitest_util.h
+++ b/chrome/browser/autofill/autofill_uitest_util.h
@@ -22,6 +22,8 @@
 void AddTestAutofillData(Browser* browser,
                          const AutofillProfile& profile,
                          const CreditCard& card);
+void WaitForPersonalDataChange(Browser* browser);
+
 }  // namespace autofill
 
 #endif  // CHROME_BROWSER_AUTOFILL_AUTOFILL_UITEST_UTIL_H_
diff --git a/chrome/browser/chrome_service.cc b/chrome/browser/chrome_service.cc
index 1891451..dec6c1e 100644
--- a/chrome/browser/chrome_service.cc
+++ b/chrome/browser/chrome_service.cc
@@ -23,11 +23,8 @@
 #include "services/service_manager/public/cpp/service_binding.h"
 
 #if defined(OS_CHROMEOS)
-#include "chrome/browser/chromeos/launchable.h"
-#if defined(USE_OZONE)
 #include "services/ws/public/cpp/input_devices/input_device_controller.h"
 #endif
-#endif
 #if BUILDFLAG(ENABLE_SPELLCHECK)
 #include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h"
 #if BUILDFLAG(HAS_SPELLCHECK_PANEL)
@@ -43,13 +40,8 @@
             {content::BrowserThread::UI});
 
 #if defined(OS_CHROMEOS)
-#if defined(USE_OZONE)
     input_device_controller_.AddInterface(&registry_, ui_task_runner);
 #endif
-    registry_.AddInterface(base::BindRepeating(&chromeos::Launchable::Bind,
-                                               base::Unretained(&launchable_)),
-                           ui_task_runner);
-#endif
     registry_.AddInterface(base::BindRepeating(
         &startup_metric_utils::StartupMetricHostImpl::Create));
 #if BUILDFLAG(ENABLE_SPELLCHECK)
@@ -118,11 +110,8 @@
       registry_with_source_info_;
 
 #if defined(OS_CHROMEOS)
-  chromeos::Launchable launchable_;
-#if defined(USE_OZONE)
   ws::InputDeviceController input_device_controller_;
 #endif
-#endif
 
   DISALLOW_COPY_AND_ASSIGN(IOThreadContext);
 };
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index b7ad1b9..29dc86bc 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -194,9 +194,6 @@
     "//gpu",
     "//gpu/ipc/host",
     "//gpu/ipc/service",
-
-    # TODO(jamescook): Eliminate use of Launchable interface.
-    "//mash/public/mojom",
     "//media",
     "//media/mojo/interfaces",
     "//mojo/public/cpp/platform",
@@ -631,8 +628,8 @@
     "child_accounts/event_based_status_reporting_service.h",
     "child_accounts/event_based_status_reporting_service_factory.cc",
     "child_accounts/event_based_status_reporting_service_factory.h",
-    "child_accounts/parent_access_code/parent_access_code_authenticator.cc",
-    "child_accounts/parent_access_code/parent_access_code_authenticator.h",
+    "child_accounts/parent_access_code/authenticator.cc",
+    "child_accounts/parent_access_code/authenticator.h",
     "child_accounts/screen_time_controller.cc",
     "child_accounts/screen_time_controller.h",
     "child_accounts/screen_time_controller_factory.cc",
@@ -1017,8 +1014,6 @@
     "input_method/input_method_syncer.h",
     "language_preferences.cc",
     "language_preferences.h",
-    "launchable.cc",
-    "launchable.h",
     "launcher_search_provider/error_reporter.cc",
     "launcher_search_provider/error_reporter.h",
     "launcher_search_provider/launcher_search_provider_service.cc",
@@ -2210,7 +2205,7 @@
     "base/file_flusher_unittest.cc",
     "certificate_provider/certificate_provider_service_unittest.cc",
     "child_accounts/event_based_status_reporting_service_unittest.cc",
-    "child_accounts/parent_access_code/parent_access_code_authenticator_unittest.cc",
+    "child_accounts/parent_access_code/authenticator_unittest.cc",
     "child_accounts/time_limit_notifier_unittest.cc",
     "child_accounts/time_limit_test_utils.cc",
     "child_accounts/usage_time_limit_processor_unittest.cc",
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.cc b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
index 231b3c7..27dbedc 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.cc
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
@@ -81,7 +81,6 @@
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/extension_resource.h"
 #include "extensions/common/host_id.h"
-#include "mash/public/mojom/launchable.mojom.h"
 #include "media/audio/sounds/sounds_manager.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/media_session/public/mojom/constants.mojom.h"
diff --git a/chrome/browser/chromeos/arc/auth/arc_auth_context.cc b/chrome/browser/chromeos/arc/auth/arc_auth_context.cc
index 27703e7..edd6436 100644
--- a/chrome/browser/chromeos/arc/auth/arc_auth_context.cc
+++ b/chrome/browser/chromeos/arc/auth/arc_auth_context.cc
@@ -104,7 +104,7 @@
 }
 
 void ArcAuthContext::OnRefreshTokenUpdatedForAccount(
-    const AccountInfo& account_info) {
+    const CoreAccountInfo& account_info) {
   // There is no need to check |is_valid| here. It is intended to avoid
   // adding the ability to query the persistent error state to the
   // IdentityManager API, which is irrelevant for this case.
diff --git a/chrome/browser/chromeos/arc/auth/arc_auth_context.h b/chrome/browser/chromeos/arc/auth/arc_auth_context.h
index fb4b279..5ab03c9 100644
--- a/chrome/browser/chromeos/arc/auth/arc_auth_context.h
+++ b/chrome/browser/chromeos/arc/auth/arc_auth_context.h
@@ -49,7 +49,7 @@
 
   // identity::IdentityManager::Observer:
   void OnRefreshTokenUpdatedForAccount(
-      const AccountInfo& account_info) override;
+      const CoreAccountInfo& account_info) override;
   void OnRefreshTokensLoaded() override;
 
   // Ubertoken fetch completion callback.
diff --git a/chrome/browser/chromeos/arc/policy/arc_android_management_checker.cc b/chrome/browser/chromeos/arc/policy/arc_android_management_checker.cc
index 1a3fe31..b66ddc2 100644
--- a/chrome/browser/chromeos/arc/policy/arc_android_management_checker.cc
+++ b/chrome/browser/chromeos/arc/policy/arc_android_management_checker.cc
@@ -98,7 +98,7 @@
 }
 
 void ArcAndroidManagementChecker::OnRefreshTokenUpdatedForAccount(
-    const AccountInfo& account_info) {
+    const CoreAccountInfo& account_info) {
   if (account_info.account_id != device_account_id_)
     return;
   OnRefreshTokensLoaded();
diff --git a/chrome/browser/chromeos/arc/policy/arc_android_management_checker.h b/chrome/browser/chromeos/arc/policy/arc_android_management_checker.h
index 168684c..61dd2eb 100644
--- a/chrome/browser/chromeos/arc/policy/arc_android_management_checker.h
+++ b/chrome/browser/chromeos/arc/policy/arc_android_management_checker.h
@@ -44,7 +44,7 @@
 
   // identity::IdentityManager::Observer:
   void OnRefreshTokenUpdatedForAccount(
-      const AccountInfo& account_info) override;
+      const CoreAccountInfo& account_info) override;
   void OnRefreshTokensLoaded() override;
 
   // Unowned pointers.
diff --git a/chrome/browser/chromeos/child_accounts/parent_access_code/authenticator.cc b/chrome/browser/chromeos/child_accounts/parent_access_code/authenticator.cc
new file mode 100644
index 0000000..69945a7
--- /dev/null
+++ b/chrome/browser/chromeos/child_accounts/parent_access_code/authenticator.cc
@@ -0,0 +1,145 @@
+// Copyright 2019 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/browser/chromeos/child_accounts/parent_access_code/authenticator.h"
+
+#include <vector>
+
+#include "base/big_endian.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+
+namespace chromeos {
+namespace parent_access {
+
+AccessCodeConfig::AccessCodeConfig(const std::string& shared_secret,
+                                   base::TimeDelta code_validity,
+                                   base::TimeDelta clock_drift_tolerance)
+    : shared_secret_(shared_secret),
+      code_validity_(code_validity),
+      clock_drift_tolerance_(clock_drift_tolerance) {
+  DCHECK(!shared_secret_.empty());
+  DCHECK(code_validity_ >= base::TimeDelta::FromSeconds(60));
+  DCHECK(code_validity_ <= base::TimeDelta::FromMinutes(60));
+  DCHECK(clock_drift_tolerance_ <= base::TimeDelta::FromMinutes(30));
+}
+
+AccessCodeConfig::AccessCodeConfig(const AccessCodeConfig& rhs) = default;
+
+AccessCodeConfig& AccessCodeConfig::operator=(const AccessCodeConfig& rhs) =
+    default;
+
+AccessCodeConfig::~AccessCodeConfig() = default;
+
+AccessCode::AccessCode(const std::string& code,
+                       base::Time valid_from,
+                       base::Time valid_to)
+    : code_(code), valid_from_(valid_from), valid_to_(valid_to) {
+  DCHECK_EQ(6u, code_.length());
+  DCHECK_GT(valid_to_, valid_from_);
+}
+
+AccessCode::AccessCode(const AccessCode&) = default;
+
+AccessCode& AccessCode::operator=(const AccessCode&) = default;
+
+AccessCode::~AccessCode() = default;
+
+bool AccessCode::operator==(const AccessCode& rhs) const {
+  return code_ == rhs.code() && valid_from_ == rhs.valid_from() &&
+         valid_to_ == rhs.valid_to();
+}
+
+bool AccessCode::operator!=(const AccessCode& rhs) const {
+  return code_ != rhs.code() || valid_from_ != rhs.valid_from() ||
+         valid_to_ != rhs.valid_to();
+}
+
+std::ostream& operator<<(std::ostream& out, const AccessCode& code) {
+  return out << code.code() << " [" << code.valid_from() << " - "
+             << code.valid_to() << "]";
+}
+
+// static
+constexpr base::TimeDelta Authenticator::kAccessCodeGranularity;
+
+Authenticator::Authenticator(const AccessCodeConfig& config) : config_(config) {
+  bool result = hmac_.Init(config_.shared_secret());
+  DCHECK(result);
+}
+
+Authenticator::~Authenticator() = default;
+
+base::Optional<AccessCode> Authenticator::Generate(base::Time timestamp) const {
+  DCHECK_LE(base::Time::UnixEpoch(), timestamp);
+
+  // We find the beginning of the interval for the given timestamp and adjust by
+  // the granularity.
+  const int64_t interval =
+      timestamp.ToJavaTime() / config_.code_validity().InMilliseconds();
+  const int64_t interval_beginning_timestamp =
+      interval * config_.code_validity().InMilliseconds();
+  const int64_t adjusted_timestamp =
+      interval_beginning_timestamp / kAccessCodeGranularity.InMilliseconds();
+
+  // The algorithm for PAC generation is using data in Big-endian byte order to
+  // feed HMAC.
+  std::string big_endian_timestamp(sizeof(adjusted_timestamp), 0);
+  base::WriteBigEndian(&big_endian_timestamp[0], adjusted_timestamp);
+
+  std::vector<uint8_t> digest(hmac_.DigestLength());
+  if (!hmac_.Sign(big_endian_timestamp, &digest[0], digest.size())) {
+    LOG(ERROR) << "Signing HMAC data to generate Parent Access Code failed";
+    return base::nullopt;
+  }
+
+  // Read 4 bytes in Big-endian order starting from |offset|.
+  const int8_t offset = digest.back() & 0xf;
+  int32_t result;
+  std::vector<uint8_t> slice(digest.begin() + offset,
+                             digest.begin() + offset + sizeof(result));
+  base::ReadBigEndian(reinterpret_cast<char*>(slice.data()), &result);
+  // Clear sign bit.
+  result &= 0x7fffffff;
+
+  const base::Time valid_from =
+      base::Time::FromJavaTime(interval_beginning_timestamp);
+  return AccessCode(base::StringPrintf("%06d", result % 1000000), valid_from,
+                    valid_from + config_.code_validity());
+}
+
+base::Optional<AccessCode> Authenticator::Validate(const std::string& code,
+                                                   base::Time timestamp) const {
+  DCHECK_LE(base::Time::UnixEpoch(), timestamp);
+
+  base::Time valid_from = timestamp - config_.clock_drift_tolerance();
+  if (valid_from < base::Time::UnixEpoch())
+    valid_from = base::Time::UnixEpoch();
+  return ValidateInRange(code, valid_from,
+                         timestamp + config_.clock_drift_tolerance());
+}
+
+base::Optional<AccessCode> Authenticator::ValidateInRange(
+    const std::string& code,
+    base::Time valid_from,
+    base::Time valid_to) const {
+  DCHECK_LE(base::Time::UnixEpoch(), valid_from);
+  DCHECK_GE(valid_to, valid_from);
+
+  const int64_t start_interval =
+      valid_from.ToJavaTime() / kAccessCodeGranularity.InMilliseconds();
+  const int64_t end_interval =
+      valid_to.ToJavaTime() / kAccessCodeGranularity.InMilliseconds();
+  for (int i = start_interval; i <= end_interval; ++i) {
+    const base::Time generation_timestamp =
+        base::Time::FromJavaTime(i * kAccessCodeGranularity.InMilliseconds());
+    base::Optional<AccessCode> pac = Generate(generation_timestamp);
+    if (pac.has_value() && pac->code() == code)
+      return pac;
+  }
+  return base::nullopt;
+}
+
+}  // namespace parent_access
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/child_accounts/parent_access_code/authenticator.h b/chrome/browser/chromeos/child_accounts/parent_access_code/authenticator.h
new file mode 100644
index 0000000..3190448
--- /dev/null
+++ b/chrome/browser/chromeos/child_accounts/parent_access_code/authenticator.h
@@ -0,0 +1,134 @@
+// Copyright 2019 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_BROWSER_CHROMEOS_CHILD_ACCOUNTS_PARENT_ACCESS_CODE_AUTHENTICATOR_H_
+#define CHROME_BROWSER_CHROMEOS_CHILD_ACCOUNTS_PARENT_ACCESS_CODE_AUTHENTICATOR_H_
+
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "crypto/hmac.h"
+
+namespace chromeos {
+namespace parent_access {
+
+// Configuration used to generate and verify parent access code.
+class AccessCodeConfig {
+ public:
+  // To create valid AccessCodeConfig:
+  // * |shared_secret| cannot be empty
+  // * |code_validity| needs to be in between 30s and 3600s
+  // * |clock_drift_tolerance| needs to be between 0 and 1800s
+  // The above restrictions are applied to AccessCodeConfig policy that is the
+  // main source of this configuration.
+  AccessCodeConfig(const std::string& shared_secret,
+                   base::TimeDelta code_validity,
+                   base::TimeDelta clock_drift_tolerance);
+  AccessCodeConfig(const AccessCodeConfig& rhs);
+  AccessCodeConfig& operator=(const AccessCodeConfig&);
+  ~AccessCodeConfig();
+
+  // Secret shared between child and parent devices.
+  const std::string& shared_secret() const { return shared_secret_; }
+
+  // Time that access code is valid for.
+  base::TimeDelta code_validity() const { return code_validity_; }
+
+  // The allowed difference between the clock on child and parent devices.
+  base::TimeDelta clock_drift_tolerance() const {
+    return clock_drift_tolerance_;
+  }
+
+ private:
+  std::string shared_secret_;
+  base::TimeDelta code_validity_;
+  base::TimeDelta clock_drift_tolerance_;
+};
+
+// Parent access code that can be used to authorize various actions on child
+// user's device.
+// Typical lifetime of the code is 10 minutes and clock difference between
+// generating and validating device is half of the code lifetime. Clock
+// difference is accounted for during code validation.
+class AccessCode {
+ public:
+  // To create valid AccessCode:
+  // * |code| needs to be 6 characters long
+  // * |valid_to| needs to be greater than |valid_from|
+  AccessCode(const std::string& code,
+             base::Time valid_from,
+             base::Time valid_to);
+  AccessCode(const AccessCode&);
+  AccessCode& operator=(const AccessCode&);
+  ~AccessCode();
+
+  // Parent access code.
+  const std::string& code() const { return code_; }
+
+  // Code validity start time.
+  base::Time valid_from() const { return valid_from_; }
+
+  // Code expiration time.
+  base::Time valid_to() const { return valid_to_; }
+
+  bool operator==(const AccessCode&) const;
+  bool operator!=(const AccessCode&) const;
+  friend std::ostream& operator<<(std::ostream&, const AccessCode&);
+
+ private:
+  std::string code_;
+  base::Time valid_from_;
+  base::Time valid_to_;
+};
+
+// Generates and validates parent access codes.
+// Does not support timestamp from before Unix Epoch.
+class Authenticator {
+ public:
+  // Granularity of which generation and verification of access code are carried
+  // out. Should not exceed code validity period.
+  static constexpr base::TimeDelta kAccessCodeGranularity =
+      base::TimeDelta::FromMinutes(1);
+
+  explicit Authenticator(const AccessCodeConfig& config);
+  ~Authenticator();
+
+  // Generates parent access code from the given |timestamp|. Returns the code
+  // if generation was successful. |timestamp| needs to be greater or equal Unix
+  // Epoch.
+
+  base::Optional<AccessCode> Generate(base::Time timestamp) const;
+
+  // Returns AccessCode structure with validity information, if |code| is
+  // valid for the given timestamp. |timestamp| needs to be greater or equal
+  // Unix Epoch.
+  base::Optional<AccessCode> Validate(const std::string& code,
+                                      base::Time timestamp) const;
+
+ private:
+  // Returns AccessCode structure with validity information, if |code| is valid
+  // for the range [|valid_from|, |valid_to|). |valid_to| needs to be greater or
+  // equal to |valid_from|. |valid_from| needs to be greater or equal Unix
+  // Epoch.
+  base::Optional<AccessCode> ValidateInRange(const std::string& code,
+                                             base::Time valid_from,
+                                             base::Time valid_to) const;
+
+  // Configuration used to generate and validate parent access code.
+  const AccessCodeConfig config_;
+
+  // Keyed-hash message authentication generator.
+  crypto::HMAC hmac_{crypto::HMAC::SHA1};
+
+  DISALLOW_COPY_AND_ASSIGN(Authenticator);
+};
+
+}  // namespace parent_access
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_CHILD_ACCOUNTS_PARENT_ACCESS_CODE_AUTHENTICATOR_H_
diff --git a/chrome/browser/chromeos/child_accounts/parent_access_code/authenticator_unittest.cc b/chrome/browser/chromeos/child_accounts/parent_access_code/authenticator_unittest.cc
new file mode 100644
index 0000000..98ee40b
--- /dev/null
+++ b/chrome/browser/chromeos/child_accounts/parent_access_code/authenticator_unittest.cc
@@ -0,0 +1,382 @@
+// Copyright 2019 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/browser/chromeos/child_accounts/parent_access_code/authenticator.h"
+
+#include <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace parent_access {
+
+namespace {
+
+constexpr char kTestSharedSecret[] = "AIfVJHITSar8keeq3779V70dWiS1xbPv8g";
+constexpr base::TimeDelta kDefaultValidity = base::TimeDelta::FromMinutes(10);
+constexpr base::TimeDelta kDefaultClockDrift = base::TimeDelta::FromMinutes(5);
+
+// Configuration that is currently used for PAC.
+AccessCodeConfig DefaultConfig() {
+  return AccessCodeConfig(kTestSharedSecret, kDefaultValidity,
+                          kDefaultClockDrift);
+}
+
+// Populates |test_values| with test Parent Access Code data (timestamp - code
+// value pairs) generated in Family Link Android app.
+void GetTestValues(std::map<base::Time, std::string>* test_values) {
+  base::Time timestamp;
+  ASSERT_TRUE(base::Time::FromString("8 Jan 2019 16:58:07 PST", &timestamp));
+  (*test_values)[timestamp] = "734261";
+  ASSERT_TRUE(base::Time::FromString("14 Jan 2019 15:35:05 PST", &timestamp));
+  (*test_values)[timestamp] = "472150";
+  ASSERT_TRUE(base::Time::FromString("14 Jan 2019 15:42:49 PST", &timestamp));
+  (*test_values)[timestamp] = "204984";
+  ASSERT_TRUE(base::Time::FromString("14 Jan 2019 15:53:01 PST", &timestamp));
+  (*test_values)[timestamp] = "157758";
+  ASSERT_TRUE(base::Time::FromString("14 Jan 2019 16:00:00 PST", &timestamp));
+  (*test_values)[timestamp] = "524186";
+}
+
+}  // namespace
+
+class ParentAccessCodeAuthenticatorTest : public testing::Test {
+ protected:
+  ParentAccessCodeAuthenticatorTest() = default;
+  ~ParentAccessCodeAuthenticatorTest() override = default;
+
+  // Verifies that |code| is valid for the given |timestamp|.
+  void Verify(base::Optional<AccessCode> code, base::Time timestamp) {
+    ASSERT_TRUE(code.has_value());
+    EXPECT_GE(timestamp, code->valid_from());
+    EXPECT_LE(timestamp, code->valid_to());
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ParentAccessCodeAuthenticatorTest);
+};
+
+TEST_F(ParentAccessCodeAuthenticatorTest, GenerateHardcodedCodeValues) {
+  // Test generation against Parent Access Code values generated in Family
+  // Link Android app.
+  std::map<base::Time, std::string> test_values;
+  ASSERT_NO_FATAL_FAILURE(GetTestValues(&test_values));
+
+  Authenticator gen(DefaultConfig());
+  for (const auto& it : test_values) {
+    base::Optional<AccessCode> code = gen.Generate(it.first);
+    ASSERT_NO_FATAL_FAILURE(Verify(code, it.first));
+    EXPECT_EQ(it.second, code->code());
+  }
+}
+
+TEST_F(ParentAccessCodeAuthenticatorTest, GenerateInTheSameTimeBucket) {
+  // Test that the same code is generated for whole time bucket defined by code
+  // validity period.
+  base::Time timestamp;
+  ASSERT_TRUE(base::Time::FromString("14 Jan 2019 15:00:00 PST", &timestamp));
+  const AccessCodeConfig config = DefaultConfig();
+
+  Authenticator gen(config);
+  base::Optional<AccessCode> first_code = gen.Generate(timestamp);
+  ASSERT_NO_FATAL_FAILURE(Verify(first_code, timestamp));
+
+  int range =
+      (config.code_validity() / Authenticator::kAccessCodeGranularity) - 1;
+  for (int i = 0; i < range; ++i) {
+    timestamp += Authenticator::kAccessCodeGranularity;
+    base::Optional<AccessCode> code = gen.Generate(timestamp);
+    ASSERT_NO_FATAL_FAILURE(Verify(code, timestamp));
+    EXPECT_EQ(*first_code, *code);
+  }
+}
+
+TEST_F(ParentAccessCodeAuthenticatorTest, GenerateInDifferentTimeBuckets) {
+  // Test that the different codes are generated for different time buckets
+  // defined by code validity period.
+  base::Time initial_timestamp;
+  ASSERT_TRUE(
+      base::Time::FromString("14 Jan 2019 15:00:00 PST", &initial_timestamp));
+
+  Authenticator gen(DefaultConfig());
+  base::Optional<AccessCode> first_code = gen.Generate(initial_timestamp);
+  ASSERT_NO_FATAL_FAILURE(Verify(first_code, initial_timestamp));
+
+  for (int i = 1; i < 10; ++i) {
+    // "Earlier" time bucket.
+    {
+      const base::Time timestamp = initial_timestamp - i * kDefaultValidity;
+      base::Optional<AccessCode> code = gen.Generate(timestamp);
+      ASSERT_NO_FATAL_FAILURE(Verify(code, timestamp));
+      EXPECT_NE(*first_code, *code);
+    }
+    // "Later" time bucket.
+    {
+      const base::Time timestamp = initial_timestamp + i * kDefaultValidity;
+      base::Optional<AccessCode> code = gen.Generate(timestamp);
+      ASSERT_NO_FATAL_FAILURE(Verify(code, timestamp));
+      EXPECT_NE(*first_code, *code);
+    }
+  }
+}
+
+TEST_F(ParentAccessCodeAuthenticatorTest, GenerateWithSameTimestamp) {
+  // Test that codes generated with the same timestamp and config are the same.
+  const AccessCodeConfig config = DefaultConfig();
+  const base::Time timestamp = base::Time::Now();
+
+  Authenticator gen1(config);
+  base::Optional<AccessCode> code1 = gen1.Generate(timestamp);
+  ASSERT_NO_FATAL_FAILURE(Verify(code1, timestamp));
+
+  Authenticator gen2(config);
+  base::Optional<AccessCode> code2 = gen2.Generate(timestamp);
+  ASSERT_NO_FATAL_FAILURE(Verify(code2, timestamp));
+
+  EXPECT_EQ(*code1, *code2);
+}
+
+TEST_F(ParentAccessCodeAuthenticatorTest, GenerateWithDifferentSharedSecret) {
+  // Test that codes generated with the different secrets are not the same.
+  const base::Time timestamp = base::Time::Now();
+
+  Authenticator gen1(AccessCodeConfig("AAAAAAAAAAAAAAAAAAA", kDefaultValidity,
+                                      kDefaultClockDrift));
+  base::Optional<AccessCode> code1 = gen1.Generate(timestamp);
+  ASSERT_NO_FATAL_FAILURE(Verify(code1, timestamp));
+
+  Authenticator gen2(AccessCodeConfig("AAAAAAAAAAAAAAAAAAB", kDefaultValidity,
+                                      kDefaultClockDrift));
+  base::Optional<AccessCode> code2 = gen2.Generate(timestamp);
+  ASSERT_NO_FATAL_FAILURE(Verify(code2, timestamp));
+
+  EXPECT_NE(*code1, *code2);
+}
+
+TEST_F(ParentAccessCodeAuthenticatorTest, GenerateWithDifferentCodeValidity) {
+  // Test that codes generated with the different validity are not the same.
+  const base::Time timestamp = base::Time::Now();
+
+  Authenticator gen1(AccessCodeConfig(
+      kTestSharedSecret, base::TimeDelta::FromMinutes(1), kDefaultClockDrift));
+  base::Optional<AccessCode> code1 = gen1.Generate(timestamp);
+  ASSERT_NO_FATAL_FAILURE(Verify(code1, timestamp));
+
+  Authenticator gen2(AccessCodeConfig(
+      kTestSharedSecret, base::TimeDelta::FromMinutes(3), kDefaultClockDrift));
+  base::Optional<AccessCode> code2 = gen2.Generate(timestamp);
+  ASSERT_NO_FATAL_FAILURE(Verify(code2, timestamp));
+
+  EXPECT_NE(*code1, *code2);
+}
+
+TEST_F(ParentAccessCodeAuthenticatorTest,
+       GenerateWihtDifferentClockDriftTolerance) {
+  // Test that clock drift tolerance does not affect code generation.
+  const base::Time timestamp = base::Time::Now();
+
+  Authenticator gen1(AccessCodeConfig(kTestSharedSecret, kDefaultValidity,
+                                      base::TimeDelta::FromMinutes(1)));
+  base::Optional<AccessCode> code1 = gen1.Generate(timestamp);
+  ASSERT_NO_FATAL_FAILURE(Verify(code1, timestamp));
+
+  Authenticator gen2(AccessCodeConfig(kTestSharedSecret, kDefaultValidity,
+                                      base::TimeDelta::FromMinutes(10)));
+  base::Optional<AccessCode> code2 = gen2.Generate(timestamp);
+  ASSERT_NO_FATAL_FAILURE(Verify(code2, timestamp));
+
+  EXPECT_EQ(*code1, *code2);
+}
+
+TEST_F(ParentAccessCodeAuthenticatorTest, ValidateHardcodedCodeValues) {
+  // Test validation against Parent Access Code values generated in Family Link
+  // Android app.
+  std::map<base::Time, std::string> test_values;
+  ASSERT_NO_FATAL_FAILURE(GetTestValues(&test_values));
+
+  Authenticator gen(AccessCodeConfig(kTestSharedSecret, kDefaultValidity,
+                                     base::TimeDelta::FromMinutes(0)));
+  for (const auto& it : test_values) {
+    base::Optional<AccessCode> code = gen.Validate(it.second, it.first);
+    ASSERT_NO_FATAL_FAILURE(Verify(code, it.first));
+    EXPECT_EQ(it.second, code->code());
+  }
+}
+
+TEST_F(ParentAccessCodeAuthenticatorTest,
+       ValidationAndGenerationOnDifferentAuthenticators) {
+  // Test validation against codes generated by separate
+  // Authenticator object in and outside of the valid time
+  // bucket.
+  const AccessCodeConfig config(kTestSharedSecret, kDefaultValidity,
+                                base::TimeDelta::FromMinutes(0));
+  Authenticator generator(config);
+  Authenticator validator(config);
+
+  base::Time generation_timestamp;
+  ASSERT_TRUE(base::Time::FromString("15 Jan 2019 00:00:00 PST",
+                                     &generation_timestamp));
+
+  base::Optional<AccessCode> generated_code =
+      generator.Generate(generation_timestamp);
+  ASSERT_NO_FATAL_FAILURE(Verify(generated_code, generation_timestamp));
+
+  // Before valid period.
+  base::Optional<AccessCode> validated_code = validator.Validate(
+      generated_code->code(),
+      generation_timestamp - base::TimeDelta::FromSeconds(1));
+  EXPECT_FALSE(validated_code);
+
+  // In valid period.
+  int range = config.code_validity() / Authenticator::kAccessCodeGranularity;
+  for (int i = 0; i < range; ++i) {
+    validated_code = validator.Validate(
+        generated_code->code(),
+        generation_timestamp + i * Authenticator::kAccessCodeGranularity);
+    ASSERT_TRUE(validated_code);
+    EXPECT_EQ(*generated_code, *validated_code);
+  }
+
+  // After valid period.
+  validated_code = validator.Validate(
+      generated_code->code(), generation_timestamp + config.code_validity());
+  EXPECT_FALSE(validated_code);
+}
+
+TEST_F(ParentAccessCodeAuthenticatorTest,
+       ValidationAndGenerationOnSameAuthenticator) {
+  // Test generation and validation on the same Authenticator
+  // object in and outside of the valid time bucket.
+  const AccessCodeConfig config(kTestSharedSecret, kDefaultValidity,
+                                base::TimeDelta::FromMinutes(0));
+  Authenticator authenticator(config);
+
+  base::Time generation_timestamp;
+  ASSERT_TRUE(base::Time::FromString("15 Jan 2019 00:00:00 PST",
+                                     &generation_timestamp));
+
+  base::Optional<AccessCode> generated_code =
+      authenticator.Generate(generation_timestamp);
+  ASSERT_NO_FATAL_FAILURE(Verify(generated_code, generation_timestamp));
+
+  // Before valid period.
+  base::Optional<AccessCode> validated_code = authenticator.Validate(
+      generated_code->code(),
+      generation_timestamp - base::TimeDelta::FromSeconds(1));
+  EXPECT_FALSE(validated_code);
+
+  // In valid period.
+  int range = config.code_validity() / Authenticator::kAccessCodeGranularity;
+  for (int i = 0; i < range; ++i) {
+    validated_code = authenticator.Validate(
+        generated_code->code(),
+        generation_timestamp + i * Authenticator::kAccessCodeGranularity);
+    ASSERT_TRUE(validated_code);
+    EXPECT_EQ(*generated_code, *validated_code);
+  }
+
+  // After valid period.
+  validated_code = authenticator.Validate(
+      generated_code->code(), generation_timestamp + config.code_validity());
+  EXPECT_FALSE(validated_code);
+}
+
+TEST_F(ParentAccessCodeAuthenticatorTest, ValidationWithClockDriftTolerance) {
+  // Test validation with clock drift tolerance.
+  Authenticator generator(DefaultConfig());
+  Authenticator validator_with_tolerance(DefaultConfig());
+  Authenticator validator_no_tolerance(AccessCodeConfig(
+      kTestSharedSecret, kDefaultValidity, base::TimeDelta::FromMinutes(0)));
+
+  // By default code will be valid [15:30:00-15:40:00).
+  // With clock drift tolerance code will be valid [15:25:00-15:45:00).
+  base::Time generation_timestamp;
+  ASSERT_TRUE(base::Time::FromString("15 Jan 2019 15:30:00 PST",
+                                     &generation_timestamp));
+
+  base::Optional<AccessCode> generated_code =
+      generator.Generate(generation_timestamp);
+  ASSERT_NO_FATAL_FAILURE(Verify(generated_code, generation_timestamp));
+
+  // Both validators accept the code in valid period.
+  int range = kDefaultValidity / Authenticator::kAccessCodeGranularity;
+  base::Time timestamp;
+  base::Optional<AccessCode> validated_code_no_tolerance;
+  base::Optional<AccessCode> validated_code_with_tolerance;
+  for (int i = 0; i < range; ++i) {
+    timestamp =
+        generation_timestamp + i * Authenticator::kAccessCodeGranularity;
+
+    validated_code_no_tolerance =
+        validator_no_tolerance.Validate(generated_code->code(), timestamp);
+    ASSERT_TRUE(validated_code_no_tolerance);
+
+    validated_code_with_tolerance =
+        validator_with_tolerance.Validate(generated_code->code(), timestamp);
+    ASSERT_TRUE(validated_code_with_tolerance);
+
+    EXPECT_EQ(*validated_code_no_tolerance, *validated_code_with_tolerance);
+  }
+
+  // Validator's device clock late by tolerated drift.
+  timestamp = generation_timestamp - kDefaultClockDrift / 2;
+  validated_code_no_tolerance =
+      validator_no_tolerance.Validate(generated_code->code(), timestamp);
+  EXPECT_FALSE(validated_code_no_tolerance);
+
+  validated_code_with_tolerance =
+      validator_with_tolerance.Validate(generated_code->code(), timestamp);
+  EXPECT_TRUE(validated_code_with_tolerance);
+
+  // Validator's device clock late outside of tolerated drift.
+  timestamp = generation_timestamp - kDefaultClockDrift -
+              base::TimeDelta::FromSeconds(1);
+  validated_code_no_tolerance =
+      validator_no_tolerance.Validate(generated_code->code(), timestamp);
+  EXPECT_FALSE(validated_code_no_tolerance);
+
+  validated_code_with_tolerance =
+      validator_with_tolerance.Validate(generated_code->code(), timestamp);
+  EXPECT_FALSE(validated_code_with_tolerance);
+
+  // Validator's device clock ahead by tolerated drift.
+  timestamp = generation_timestamp + kDefaultValidity + kDefaultClockDrift / 2;
+  validated_code_no_tolerance =
+      validator_no_tolerance.Validate(generated_code->code(), timestamp);
+  EXPECT_FALSE(validated_code_no_tolerance);
+
+  validated_code_with_tolerance =
+      validator_with_tolerance.Validate(generated_code->code(), timestamp);
+  EXPECT_TRUE(validated_code_with_tolerance);
+
+  // Validator's device clock ahead outside of tolerated drift.
+  timestamp = generation_timestamp + kDefaultValidity + kDefaultClockDrift;
+  validated_code_no_tolerance =
+      validator_no_tolerance.Validate(generated_code->code(), timestamp);
+  EXPECT_FALSE(validated_code_no_tolerance);
+
+  validated_code_with_tolerance =
+      validator_with_tolerance.Validate(generated_code->code(), timestamp);
+  EXPECT_FALSE(validated_code_with_tolerance);
+}
+
+TEST_F(ParentAccessCodeAuthenticatorTest, UnixEpoch) {
+  // Test authenticator with Unix Epoch timestamp.
+  const base::Time unix_epoch = base::Time::UnixEpoch();
+
+  Authenticator authenticator(DefaultConfig());
+  base::Optional<AccessCode> generated = authenticator.Generate(unix_epoch);
+  ASSERT_NO_FATAL_FAILURE(Verify(generated, unix_epoch));
+  base::Optional<AccessCode> validated =
+      authenticator.Validate(generated->code(), unix_epoch);
+  ASSERT_NO_FATAL_FAILURE(Verify(validated, unix_epoch));
+  EXPECT_EQ(generated, validated);
+}
+
+}  // namespace parent_access
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/child_accounts/parent_access_code/parent_access_code_authenticator.cc b/chrome/browser/chromeos/child_accounts/parent_access_code/parent_access_code_authenticator.cc
deleted file mode 100644
index caa84a19..0000000
--- a/chrome/browser/chromeos/child_accounts/parent_access_code/parent_access_code_authenticator.cc
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2019 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/browser/chromeos/child_accounts/parent_access_code/parent_access_code_authenticator.h"
-
-#include <vector>
-
-#include "base/big_endian.h"
-#include "base/logging.h"
-#include "base/strings/stringprintf.h"
-
-namespace chromeos {
-
-ParentAccessCodeConfig::ParentAccessCodeConfig(
-    const std::string& shared_secret,
-    base::TimeDelta code_validity,
-    base::TimeDelta clock_drift_tolerance)
-    : shared_secret_(shared_secret),
-      code_validity_(code_validity),
-      clock_drift_tolerance_(clock_drift_tolerance) {
-  DCHECK(!shared_secret_.empty());
-  DCHECK(code_validity_ >= base::TimeDelta::FromSeconds(60));
-  DCHECK(code_validity_ <= base::TimeDelta::FromMinutes(60));
-  DCHECK(clock_drift_tolerance_ <= base::TimeDelta::FromMinutes(30));
-}
-
-ParentAccessCodeConfig::ParentAccessCodeConfig(
-    const ParentAccessCodeConfig& rhs) = default;
-
-ParentAccessCodeConfig& ParentAccessCodeConfig::operator=(
-    const ParentAccessCodeConfig& rhs) = default;
-
-ParentAccessCodeConfig::~ParentAccessCodeConfig() = default;
-
-ParentAccessCode::ParentAccessCode(const std::string& code,
-                                   base::Time valid_from,
-                                   base::Time valid_to)
-    : code_(code), valid_from_(valid_from), valid_to_(valid_to) {
-  DCHECK_EQ(6u, code_.length());
-  DCHECK_GT(valid_to_, valid_from_);
-}
-
-ParentAccessCode::ParentAccessCode(const ParentAccessCode&) = default;
-
-ParentAccessCode& ParentAccessCode::operator=(const ParentAccessCode&) =
-    default;
-
-ParentAccessCode::~ParentAccessCode() = default;
-
-bool ParentAccessCode::operator==(const ParentAccessCode& rhs) const {
-  return code_ == rhs.code() && valid_from_ == rhs.valid_from() &&
-         valid_to_ == rhs.valid_to();
-}
-
-bool ParentAccessCode::operator!=(const ParentAccessCode& rhs) const {
-  return code_ != rhs.code() || valid_from_ != rhs.valid_from() ||
-         valid_to_ != rhs.valid_to();
-}
-
-std::ostream& operator<<(std::ostream& out, const ParentAccessCode& code) {
-  return out << code.code() << " [" << code.valid_from() << " - "
-             << code.valid_to() << "]";
-}
-
-// static
-constexpr base::TimeDelta ParentAccessCodeAuthenticator::kCodeGranularity;
-
-ParentAccessCodeAuthenticator::ParentAccessCodeAuthenticator(
-    const ParentAccessCodeConfig& config)
-    : config_(config) {
-  bool result = hmac_.Init(config_.shared_secret());
-  DCHECK(result);
-}
-
-ParentAccessCodeAuthenticator::~ParentAccessCodeAuthenticator() = default;
-
-base::Optional<ParentAccessCode> ParentAccessCodeAuthenticator::Generate(
-    base::Time timestamp) {
-  DCHECK_LE(base::Time::UnixEpoch(), timestamp);
-
-  // We find the beginning of the interval for the given timestamp and adjust by
-  // the granularity.
-  const int64_t interval =
-      timestamp.ToJavaTime() / config_.code_validity().InMilliseconds();
-  const int64_t interval_beginning_timestamp =
-      interval * config_.code_validity().InMilliseconds();
-  const int64_t adjusted_timestamp =
-      interval_beginning_timestamp / kCodeGranularity.InMilliseconds();
-
-  // The algorithm for PAC generation is using data in Big-endian byte order to
-  // feed HMAC.
-  std::string big_endian_timestamp(sizeof(adjusted_timestamp), 0);
-  base::WriteBigEndian(&big_endian_timestamp[0], adjusted_timestamp);
-
-  std::vector<uint8_t> digest(hmac_.DigestLength());
-  if (!hmac_.Sign(big_endian_timestamp, &digest[0], digest.size())) {
-    LOG(ERROR) << "Signing HMAC data to generate Parent Access Code failed";
-    return base::nullopt;
-  }
-
-  // Read 4 bytes in Big-endian order starting from |offset|.
-  const int8_t offset = digest.back() & 0xf;
-  int32_t result;
-  std::vector<uint8_t> slice(digest.begin() + offset,
-                             digest.begin() + offset + sizeof(result));
-  base::ReadBigEndian(reinterpret_cast<char*>(slice.data()), &result);
-  // Clear sign bit.
-  result &= 0x7fffffff;
-
-  const base::Time valid_from =
-      base::Time::FromJavaTime(interval_beginning_timestamp);
-  return ParentAccessCode(base::StringPrintf("%06d", result % 1000000),
-                          valid_from, valid_from + config_.code_validity());
-}
-
-base::Optional<ParentAccessCode> ParentAccessCodeAuthenticator::Validate(
-    const std::string& code,
-    base::Time timestamp) {
-  DCHECK_LE(base::Time::UnixEpoch(), timestamp);
-
-  base::Time valid_from = timestamp - config_.clock_drift_tolerance();
-  if (valid_from < base::Time::UnixEpoch())
-    valid_from = base::Time::UnixEpoch();
-  return ValidateInRange(code, valid_from,
-                         timestamp + config_.clock_drift_tolerance());
-}
-
-base::Optional<ParentAccessCode> ParentAccessCodeAuthenticator::ValidateInRange(
-    const std::string& code,
-    base::Time valid_from,
-    base::Time valid_to) {
-  DCHECK_LE(base::Time::UnixEpoch(), valid_from);
-  DCHECK_GE(valid_to, valid_from);
-
-  const int64_t start_interval =
-      valid_from.ToJavaTime() / kCodeGranularity.InMilliseconds();
-  const int64_t end_interval =
-      valid_to.ToJavaTime() / kCodeGranularity.InMilliseconds();
-  for (int i = start_interval; i <= end_interval; ++i) {
-    const base::Time generation_timestamp =
-        base::Time::FromJavaTime(i * kCodeGranularity.InMilliseconds());
-    base::Optional<ParentAccessCode> pac = Generate(generation_timestamp);
-    if (pac.has_value() && pac->code() == code)
-      return pac;
-  }
-  return base::nullopt;
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/child_accounts/parent_access_code/parent_access_code_authenticator.h b/chrome/browser/chromeos/child_accounts/parent_access_code/parent_access_code_authenticator.h
deleted file mode 100644
index 103d009..0000000
--- a/chrome/browser/chromeos/child_accounts/parent_access_code/parent_access_code_authenticator.h
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2019 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_BROWSER_CHROMEOS_CHILD_ACCOUNTS_PARENT_ACCESS_CODE_PARENT_ACCESS_CODE_AUTHENTICATOR_H_
-#define CHROME_BROWSER_CHROMEOS_CHILD_ACCOUNTS_PARENT_ACCESS_CODE_PARENT_ACCESS_CODE_AUTHENTICATOR_H_
-
-#include <memory>
-#include <ostream>
-#include <string>
-
-#include "base/macros.h"
-#include "base/optional.h"
-#include "base/time/time.h"
-#include "crypto/hmac.h"
-
-namespace chromeos {
-
-// Configuration used to generate and verify Parent Access Code.
-class ParentAccessCodeConfig {
- public:
-  // To create valid ParentAccessCodeConfig:
-  // * |shared_secret| cannot be empty
-  // * |code_validity| needs to be in between 30s and 3600s
-  // * |clock_drift_tolerance| needs to be between 0 and 1800s
-  // The above restrictions are applied to ParentAccessCodeConfig policy that is
-  // the main source of this configuration.
-  ParentAccessCodeConfig(const std::string& shared_secret,
-                         base::TimeDelta code_validity,
-                         base::TimeDelta clock_drift_tolerance);
-  ParentAccessCodeConfig(const ParentAccessCodeConfig& rhs);
-  ParentAccessCodeConfig& operator=(const ParentAccessCodeConfig&);
-  ~ParentAccessCodeConfig();
-
-  // Secret shared between child and parent devices.
-  const std::string& shared_secret() const { return shared_secret_; }
-
-  // Time that access code is valid for.
-  base::TimeDelta code_validity() const { return code_validity_; }
-
-  // The allowed difference between the clock on child and parent devices.
-  base::TimeDelta clock_drift_tolerance() const {
-    return clock_drift_tolerance_;
-  }
-
- private:
-  std::string shared_secret_;
-  base::TimeDelta code_validity_;
-  base::TimeDelta clock_drift_tolerance_;
-};
-
-// Parent Access Code that can be used to authorize various actions on child
-// user's device.
-// Typical lifetime of the code is 10 minutes and clock difference between
-// generating and validating device is half of the code lifetime. Clock
-// difference is accounted for during code validation.
-class ParentAccessCode {
- public:
-  // To create valid ParentAccessCode:
-  // * |code| needs to be 6 characters long
-  // * |valid_to| needs to be greater than |valid_from|
-  ParentAccessCode(const std::string& code,
-                   base::Time valid_from,
-                   base::Time valid_to);
-  ParentAccessCode(const ParentAccessCode&);
-  ParentAccessCode& operator=(const ParentAccessCode&);
-  ~ParentAccessCode();
-
-  // Parent access code.
-  const std::string& code() const { return code_; }
-
-  // Code validity start time.
-  base::Time valid_from() const { return valid_from_; }
-
-  // Code expiration time.
-  base::Time valid_to() const { return valid_to_; }
-
-  bool operator==(const ParentAccessCode&) const;
-  bool operator!=(const ParentAccessCode&) const;
-  friend std::ostream& operator<<(std::ostream&, const ParentAccessCode&);
-
- private:
-  std::string code_;
-  base::Time valid_from_;
-  base::Time valid_to_;
-};
-
-// Generates and validates ParentAccessCodes.
-// Does not support timestamp from before Unix Epoch.
-class ParentAccessCodeAuthenticator {
- public:
-  // Granularity of which generation and verification are carried out. Should
-  // not exceed code validity period.
-  static constexpr base::TimeDelta kCodeGranularity =
-      base::TimeDelta::FromMinutes(1);
-
-  explicit ParentAccessCodeAuthenticator(const ParentAccessCodeConfig& config);
-  ~ParentAccessCodeAuthenticator();
-
-  // Generates Parent Access Code from the given |timestamp|. Returns the code
-  // if generation was successful. |timestamp| needs to be greater or equal Unix
-  // Epoch.
-
-  base::Optional<ParentAccessCode> Generate(base::Time timestamp);
-
-  // Returns ParentAccessCode structure with validity information, if |code| is
-  // valid for the given timestamp. |timestamp| needs to be greater or equal
-  // Unix Epoch.
-  base::Optional<ParentAccessCode> Validate(const std::string& code,
-                                            base::Time timestamp);
-
- private:
-  // Returns ParentAccessCode structure with validity information, if |code| is
-  // valid for the range [|valid_from|, |valid_to|). |valid_to| needs to be
-  // greater or equal to |valid_from|. |valid_from| needs to be greater or equal
-  // Unix Epoch.
-  base::Optional<ParentAccessCode> ValidateInRange(const std::string& code,
-                                                   base::Time valid_from,
-                                                   base::Time valid_to);
-
-  // Configuration used to generate and validate parent access code.
-  const ParentAccessCodeConfig config_;
-
-  // Keyed-hash message authentication generator.
-  crypto::HMAC hmac_{crypto::HMAC::SHA1};
-
-  DISALLOW_COPY_AND_ASSIGN(ParentAccessCodeAuthenticator);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_CHILD_ACCOUNTS_PARENT_ACCESS_CODE_PARENT_ACCESS_CODE_AUTHENTICATOR_H_
diff --git a/chrome/browser/chromeos/child_accounts/parent_access_code/parent_access_code_authenticator_unittest.cc b/chrome/browser/chromeos/child_accounts/parent_access_code/parent_access_code_authenticator_unittest.cc
deleted file mode 100644
index c8ad5fa..0000000
--- a/chrome/browser/chromeos/child_accounts/parent_access_code/parent_access_code_authenticator_unittest.cc
+++ /dev/null
@@ -1,387 +0,0 @@
-// Copyright 2019 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/browser/chromeos/child_accounts/parent_access_code/parent_access_code_authenticator.h"
-
-#include <map>
-#include <string>
-
-#include "base/macros.h"
-#include "base/optional.h"
-#include "base/time/time.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace chromeos {
-
-namespace {
-
-constexpr char kTestSharedSecret[] = "AIfVJHITSar8keeq3779V70dWiS1xbPv8g";
-constexpr base::TimeDelta kDefaultValidity = base::TimeDelta::FromMinutes(10);
-constexpr base::TimeDelta kDefaultClockDrift = base::TimeDelta::FromMinutes(5);
-
-// Configuration that is currently used for PAC.
-ParentAccessCodeConfig DefaultConfig() {
-  return ParentAccessCodeConfig(kTestSharedSecret, kDefaultValidity,
-                                kDefaultClockDrift);
-}
-
-// Populates |test_values| with test Parent Access Code data (timestamp - code
-// value pairs) generated in Family Link Android app.
-void GetTestValues(std::map<base::Time, std::string>* test_values) {
-  base::Time timestamp;
-  ASSERT_TRUE(base::Time::FromString("8 Jan 2019 16:58:07 PST", &timestamp));
-  (*test_values)[timestamp] = "734261";
-  ASSERT_TRUE(base::Time::FromString("14 Jan 2019 15:35:05 PST", &timestamp));
-  (*test_values)[timestamp] = "472150";
-  ASSERT_TRUE(base::Time::FromString("14 Jan 2019 15:42:49 PST", &timestamp));
-  (*test_values)[timestamp] = "204984";
-  ASSERT_TRUE(base::Time::FromString("14 Jan 2019 15:53:01 PST", &timestamp));
-  (*test_values)[timestamp] = "157758";
-  ASSERT_TRUE(base::Time::FromString("14 Jan 2019 16:00:00 PST", &timestamp));
-  (*test_values)[timestamp] = "524186";
-}
-
-}  // namespace
-
-class ParentAccessCodeAuthenticatorTest : public testing::Test {
- protected:
-  ParentAccessCodeAuthenticatorTest() = default;
-  ~ParentAccessCodeAuthenticatorTest() override = default;
-
-  // Verifies that |code| is valid for the given |timestamp|.
-  void Verify(base::Optional<ParentAccessCode> code, base::Time timestamp) {
-    ASSERT_TRUE(code.has_value());
-    EXPECT_GE(timestamp, code->valid_from());
-    EXPECT_LE(timestamp, code->valid_to());
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ParentAccessCodeAuthenticatorTest);
-};
-
-TEST_F(ParentAccessCodeAuthenticatorTest, GenerateHardcodedCodeValues) {
-  // Test generation against Parent Access Code values generated in Family
-  // Link Android app.
-  std::map<base::Time, std::string> test_values;
-  ASSERT_NO_FATAL_FAILURE(GetTestValues(&test_values));
-
-  ParentAccessCodeAuthenticator gen(DefaultConfig());
-  for (const auto& it : test_values) {
-    base::Optional<ParentAccessCode> code = gen.Generate(it.first);
-    ASSERT_NO_FATAL_FAILURE(Verify(code, it.first));
-    EXPECT_EQ(it.second, code->code());
-  }
-}
-
-TEST_F(ParentAccessCodeAuthenticatorTest, GenerateInTheSameTimeBucket) {
-  // Test that the same code is generated for whole time bucket defined by code
-  // validity period.
-  base::Time timestamp;
-  ASSERT_TRUE(base::Time::FromString("14 Jan 2019 15:00:00 PST", &timestamp));
-  const ParentAccessCodeConfig config = DefaultConfig();
-
-  ParentAccessCodeAuthenticator gen(config);
-  base::Optional<ParentAccessCode> first_code = gen.Generate(timestamp);
-  ASSERT_NO_FATAL_FAILURE(Verify(first_code, timestamp));
-
-  int range = (config.code_validity() /
-               ParentAccessCodeAuthenticator::kCodeGranularity) -
-              1;
-  for (int i = 0; i < range; ++i) {
-    timestamp += ParentAccessCodeAuthenticator::kCodeGranularity;
-    base::Optional<ParentAccessCode> code = gen.Generate(timestamp);
-    ASSERT_NO_FATAL_FAILURE(Verify(code, timestamp));
-    EXPECT_EQ(*first_code, *code);
-  }
-}
-
-TEST_F(ParentAccessCodeAuthenticatorTest, GenerateInDifferentTimeBuckets) {
-  // Test that the different codes are generated for different time buckets
-  // defined by code validity period.
-  base::Time initial_timestamp;
-  ASSERT_TRUE(
-      base::Time::FromString("14 Jan 2019 15:00:00 PST", &initial_timestamp));
-
-  ParentAccessCodeAuthenticator gen(DefaultConfig());
-  base::Optional<ParentAccessCode> first_code = gen.Generate(initial_timestamp);
-  ASSERT_NO_FATAL_FAILURE(Verify(first_code, initial_timestamp));
-
-  for (int i = 1; i < 10; ++i) {
-    // "Earlier" time bucket.
-    {
-      const base::Time timestamp = initial_timestamp - i * kDefaultValidity;
-      base::Optional<ParentAccessCode> code = gen.Generate(timestamp);
-      ASSERT_NO_FATAL_FAILURE(Verify(code, timestamp));
-      EXPECT_NE(*first_code, *code);
-    }
-    // "Later" time bucket.
-    {
-      const base::Time timestamp = initial_timestamp + i * kDefaultValidity;
-      base::Optional<ParentAccessCode> code = gen.Generate(timestamp);
-      ASSERT_NO_FATAL_FAILURE(Verify(code, timestamp));
-      EXPECT_NE(*first_code, *code);
-    }
-  }
-}
-
-TEST_F(ParentAccessCodeAuthenticatorTest, GenerateWithSameTimestamp) {
-  // Test that codes generated with the same timestamp and config are the same.
-  const ParentAccessCodeConfig config = DefaultConfig();
-  const base::Time timestamp = base::Time::Now();
-
-  ParentAccessCodeAuthenticator gen1(config);
-  base::Optional<ParentAccessCode> code1 = gen1.Generate(timestamp);
-  ASSERT_NO_FATAL_FAILURE(Verify(code1, timestamp));
-
-  ParentAccessCodeAuthenticator gen2(config);
-  base::Optional<ParentAccessCode> code2 = gen2.Generate(timestamp);
-  ASSERT_NO_FATAL_FAILURE(Verify(code2, timestamp));
-
-  EXPECT_EQ(*code1, *code2);
-}
-
-TEST_F(ParentAccessCodeAuthenticatorTest, GenerateWithDifferentSharedSecret) {
-  // Test that codes generated with the different secrets are not the same.
-  const base::Time timestamp = base::Time::Now();
-
-  ParentAccessCodeAuthenticator gen1(ParentAccessCodeConfig(
-      "AAAAAAAAAAAAAAAAAAA", kDefaultValidity, kDefaultClockDrift));
-  base::Optional<ParentAccessCode> code1 = gen1.Generate(timestamp);
-  ASSERT_NO_FATAL_FAILURE(Verify(code1, timestamp));
-
-  ParentAccessCodeAuthenticator gen2(ParentAccessCodeConfig(
-      "AAAAAAAAAAAAAAAAAAB", kDefaultValidity, kDefaultClockDrift));
-  base::Optional<ParentAccessCode> code2 = gen2.Generate(timestamp);
-  ASSERT_NO_FATAL_FAILURE(Verify(code2, timestamp));
-
-  EXPECT_NE(*code1, *code2);
-}
-
-TEST_F(ParentAccessCodeAuthenticatorTest, GenerateWithDifferentCodeValidity) {
-  // Test that codes generated with the different validity are not the same.
-  const base::Time timestamp = base::Time::Now();
-
-  ParentAccessCodeAuthenticator gen1(ParentAccessCodeConfig(
-      kTestSharedSecret, base::TimeDelta::FromMinutes(1), kDefaultClockDrift));
-  base::Optional<ParentAccessCode> code1 = gen1.Generate(timestamp);
-  ASSERT_NO_FATAL_FAILURE(Verify(code1, timestamp));
-
-  ParentAccessCodeAuthenticator gen2(ParentAccessCodeConfig(
-      kTestSharedSecret, base::TimeDelta::FromMinutes(3), kDefaultClockDrift));
-  base::Optional<ParentAccessCode> code2 = gen2.Generate(timestamp);
-  ASSERT_NO_FATAL_FAILURE(Verify(code2, timestamp));
-
-  EXPECT_NE(*code1, *code2);
-}
-
-TEST_F(ParentAccessCodeAuthenticatorTest,
-       GenerateWihtDifferentClockDriftTolerance) {
-  // Test that clock drift tolerance does not affect code generation.
-  const base::Time timestamp = base::Time::Now();
-
-  ParentAccessCodeAuthenticator gen1(ParentAccessCodeConfig(
-      kTestSharedSecret, kDefaultValidity, base::TimeDelta::FromMinutes(1)));
-  base::Optional<ParentAccessCode> code1 = gen1.Generate(timestamp);
-  ASSERT_NO_FATAL_FAILURE(Verify(code1, timestamp));
-
-  ParentAccessCodeAuthenticator gen2(ParentAccessCodeConfig(
-      kTestSharedSecret, kDefaultValidity, base::TimeDelta::FromMinutes(10)));
-  base::Optional<ParentAccessCode> code2 = gen2.Generate(timestamp);
-  ASSERT_NO_FATAL_FAILURE(Verify(code2, timestamp));
-
-  EXPECT_EQ(*code1, *code2);
-}
-
-TEST_F(ParentAccessCodeAuthenticatorTest, ValidateHardcodedCodeValues) {
-  // Test validation against Parent Access Code values generated in Family Link
-  // Android app.
-  std::map<base::Time, std::string> test_values;
-  ASSERT_NO_FATAL_FAILURE(GetTestValues(&test_values));
-
-  ParentAccessCodeAuthenticator gen(ParentAccessCodeConfig(
-      kTestSharedSecret, kDefaultValidity, base::TimeDelta::FromMinutes(0)));
-  for (const auto& it : test_values) {
-    base::Optional<ParentAccessCode> code = gen.Validate(it.second, it.first);
-    ASSERT_NO_FATAL_FAILURE(Verify(code, it.first));
-    EXPECT_EQ(it.second, code->code());
-  }
-}
-
-TEST_F(ParentAccessCodeAuthenticatorTest,
-       ValidationAndGenerationOnDifferentAuthenticators) {
-  // Test validation against codes generated by separate
-  // ParentAccessCodeAuthenticator object in and outside of the valid time
-  // bucket.
-  const ParentAccessCodeConfig config(kTestSharedSecret, kDefaultValidity,
-                                      base::TimeDelta::FromMinutes(0));
-  ParentAccessCodeAuthenticator generator(config);
-  ParentAccessCodeAuthenticator validator(config);
-
-  base::Time generation_timestamp;
-  ASSERT_TRUE(base::Time::FromString("15 Jan 2019 00:00:00 PST",
-                                     &generation_timestamp));
-
-  base::Optional<ParentAccessCode> generated_code =
-      generator.Generate(generation_timestamp);
-  ASSERT_NO_FATAL_FAILURE(Verify(generated_code, generation_timestamp));
-
-  // Before valid period.
-  base::Optional<ParentAccessCode> validated_code = validator.Validate(
-      generated_code->code(),
-      generation_timestamp - base::TimeDelta::FromSeconds(1));
-  EXPECT_FALSE(validated_code);
-
-  // In valid period.
-  int range =
-      config.code_validity() / ParentAccessCodeAuthenticator::kCodeGranularity;
-  for (int i = 0; i < range; ++i) {
-    validated_code = validator.Validate(
-        generated_code->code(),
-        generation_timestamp +
-            i * ParentAccessCodeAuthenticator::kCodeGranularity);
-    ASSERT_TRUE(validated_code);
-    EXPECT_EQ(*generated_code, *validated_code);
-  }
-
-  // After valid period.
-  validated_code = validator.Validate(
-      generated_code->code(), generation_timestamp + config.code_validity());
-  EXPECT_FALSE(validated_code);
-}
-
-TEST_F(ParentAccessCodeAuthenticatorTest,
-       ValidationAndGenerationOnSameAuthenticator) {
-  // Test generation and validation on the same ParentAccessCodeAuthenticator
-  // object in and outside of the valid time bucket.
-  const ParentAccessCodeConfig config(kTestSharedSecret, kDefaultValidity,
-                                      base::TimeDelta::FromMinutes(0));
-  ParentAccessCodeAuthenticator authenticator(config);
-
-  base::Time generation_timestamp;
-  ASSERT_TRUE(base::Time::FromString("15 Jan 2019 00:00:00 PST",
-                                     &generation_timestamp));
-
-  base::Optional<ParentAccessCode> generated_code =
-      authenticator.Generate(generation_timestamp);
-  ASSERT_NO_FATAL_FAILURE(Verify(generated_code, generation_timestamp));
-
-  // Before valid period.
-  base::Optional<ParentAccessCode> validated_code = authenticator.Validate(
-      generated_code->code(),
-      generation_timestamp - base::TimeDelta::FromSeconds(1));
-  EXPECT_FALSE(validated_code);
-
-  // In valid period.
-  int range =
-      config.code_validity() / ParentAccessCodeAuthenticator::kCodeGranularity;
-  for (int i = 0; i < range; ++i) {
-    validated_code = authenticator.Validate(
-        generated_code->code(),
-        generation_timestamp +
-            i * ParentAccessCodeAuthenticator::kCodeGranularity);
-    ASSERT_TRUE(validated_code);
-    EXPECT_EQ(*generated_code, *validated_code);
-  }
-
-  // After valid period.
-  validated_code = authenticator.Validate(
-      generated_code->code(), generation_timestamp + config.code_validity());
-  EXPECT_FALSE(validated_code);
-}
-
-TEST_F(ParentAccessCodeAuthenticatorTest, ValidationWithClockDriftTolerance) {
-  // Test validation with clock drift tolerance.
-  ParentAccessCodeAuthenticator generator(DefaultConfig());
-  ParentAccessCodeAuthenticator validator_with_tolerance(DefaultConfig());
-  ParentAccessCodeAuthenticator validator_no_tolerance(ParentAccessCodeConfig(
-      kTestSharedSecret, kDefaultValidity, base::TimeDelta::FromMinutes(0)));
-
-  // By default code will be valid [15:30:00-15:40:00).
-  // With clock drift tolerance code will be valid [15:25:00-15:45:00).
-  base::Time generation_timestamp;
-  ASSERT_TRUE(base::Time::FromString("15 Jan 2019 15:30:00 PST",
-                                     &generation_timestamp));
-
-  base::Optional<ParentAccessCode> generated_code =
-      generator.Generate(generation_timestamp);
-  ASSERT_NO_FATAL_FAILURE(Verify(generated_code, generation_timestamp));
-
-  // Both validators accept the code in valid period.
-  int range =
-      kDefaultValidity / ParentAccessCodeAuthenticator::kCodeGranularity;
-  base::Time timestamp;
-  base::Optional<ParentAccessCode> validated_code_no_tolerance;
-  base::Optional<ParentAccessCode> validated_code_with_tolerance;
-  for (int i = 0; i < range; ++i) {
-    timestamp = generation_timestamp +
-                i * ParentAccessCodeAuthenticator::kCodeGranularity;
-
-    validated_code_no_tolerance =
-        validator_no_tolerance.Validate(generated_code->code(), timestamp);
-    ASSERT_TRUE(validated_code_no_tolerance);
-
-    validated_code_with_tolerance =
-        validator_with_tolerance.Validate(generated_code->code(), timestamp);
-    ASSERT_TRUE(validated_code_with_tolerance);
-
-    EXPECT_EQ(*validated_code_no_tolerance, *validated_code_with_tolerance);
-  }
-
-  // Validator's device clock late by tolerated drift.
-  timestamp = generation_timestamp - kDefaultClockDrift / 2;
-  validated_code_no_tolerance =
-      validator_no_tolerance.Validate(generated_code->code(), timestamp);
-  EXPECT_FALSE(validated_code_no_tolerance);
-
-  validated_code_with_tolerance =
-      validator_with_tolerance.Validate(generated_code->code(), timestamp);
-  EXPECT_TRUE(validated_code_with_tolerance);
-
-  // Validator's device clock late outside of tolerated drift.
-  timestamp = generation_timestamp - kDefaultClockDrift -
-              base::TimeDelta::FromSeconds(1);
-  validated_code_no_tolerance =
-      validator_no_tolerance.Validate(generated_code->code(), timestamp);
-  EXPECT_FALSE(validated_code_no_tolerance);
-
-  validated_code_with_tolerance =
-      validator_with_tolerance.Validate(generated_code->code(), timestamp);
-  EXPECT_FALSE(validated_code_with_tolerance);
-
-  // Validator's device clock ahead by tolerated drift.
-  timestamp = generation_timestamp + kDefaultValidity + kDefaultClockDrift / 2;
-  validated_code_no_tolerance =
-      validator_no_tolerance.Validate(generated_code->code(), timestamp);
-  EXPECT_FALSE(validated_code_no_tolerance);
-
-  validated_code_with_tolerance =
-      validator_with_tolerance.Validate(generated_code->code(), timestamp);
-  EXPECT_TRUE(validated_code_with_tolerance);
-
-  // Validator's device clock ahead outside of tolerated drift.
-  timestamp = generation_timestamp + kDefaultValidity + kDefaultClockDrift;
-  validated_code_no_tolerance =
-      validator_no_tolerance.Validate(generated_code->code(), timestamp);
-  EXPECT_FALSE(validated_code_no_tolerance);
-
-  validated_code_with_tolerance =
-      validator_with_tolerance.Validate(generated_code->code(), timestamp);
-  EXPECT_FALSE(validated_code_with_tolerance);
-}
-
-TEST_F(ParentAccessCodeAuthenticatorTest, UnixEpoch) {
-  // Test authenticator with Unix Epoch timestamp.
-  const base::Time unix_epoch = base::Time::UnixEpoch();
-
-  ParentAccessCodeAuthenticator authenticator(DefaultConfig());
-  base::Optional<ParentAccessCode> generated =
-      authenticator.Generate(unix_epoch);
-  ASSERT_NO_FATAL_FAILURE(Verify(generated, unix_epoch));
-  base::Optional<ParentAccessCode> validated =
-      authenticator.Validate(generated->code(), unix_epoch);
-  ASSERT_NO_FATAL_FAILURE(Verify(validated, unix_epoch));
-  EXPECT_EQ(generated, validated);
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
index f56ec26..665e83e 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
@@ -510,6 +510,7 @@
                       TestCase("openQuickViewAndroid"),
                       TestCase("openQuickViewCrostini"),
                       TestCase("openQuickViewUsb"),
+                      TestCase("openQuickViewRemovablePartitions"),
                       TestCase("openQuickViewMtp"),
                       TestCase("pressEnterOnInfoBoxToOpenClose"),
                       TestCase("closeQuickView"),
diff --git a/chrome/browser/chromeos/file_manager/video_player_browsertest.cc b/chrome/browser/chromeos/file_manager/video_player_browsertest.cc
index 8922e2f..158719b 100644
--- a/chrome/browser/chromeos/file_manager/video_player_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/video_player_browsertest.cc
@@ -65,6 +65,14 @@
   StartTest();
 }
 
+IN_PROC_BROWSER_TEST_F(VideoPlayerBrowserTest, OpenVideoWithSubtitle) {
+  set_test_case_name("openVideoWithSubtitle");
+}
+
+IN_PROC_BROWSER_TEST_F(VideoPlayerBrowserTest, OpenVideoWithoutSubtitle) {
+  set_test_case_name("openVideoWithoutSubtitle");
+}
+
 IN_PROC_BROWSER_TEST_F(VideoPlayerBrowserTest, CheckInitialElements) {
   set_test_case_name("checkInitialElements");
   StartTest();
diff --git a/chrome/browser/chromeos/launchable.cc b/chrome/browser/chromeos/launchable.cc
deleted file mode 100644
index c3d3701..0000000
--- a/chrome/browser/chromeos/launchable.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2017 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/browser/chromeos/launchable.h"
-
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/browser_commands.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/browser_window.h"
-
-namespace chromeos {
-
-Launchable::Launchable() {}
-Launchable::~Launchable() {}
-
-void Launchable::Bind(mash::mojom::LaunchableRequest request) {
-  bindings_.AddBinding(this, std::move(request));
-}
-
-void Launchable::Launch(uint32_t what, mash::mojom::LaunchMode how) {
-  bool is_incognito;
-  switch (what) {
-    case mash::mojom::kWindow:
-      is_incognito = false;
-      break;
-    case mash::mojom::kIncognitoWindow:
-      is_incognito = true;
-      break;
-    default:
-      NOTREACHED();
-  }
-
-  bool reuse = how != mash::mojom::LaunchMode::MAKE_NEW;
-  if (reuse) {
-    Profile* profile = ProfileManager::GetActiveUserProfile();
-    Browser* browser = chrome::FindTabbedBrowser(
-        is_incognito ? profile->GetOffTheRecordProfile() : profile, false);
-    if (browser) {
-      browser->window()->Show();
-      return;
-    }
-  }
-
-  CreateNewWindowImpl(is_incognito);
-}
-
-void Launchable::CreateNewWindowImpl(bool is_incognito) {
-  Profile* profile = ProfileManager::GetActiveUserProfile();
-  chrome::NewEmptyWindow(is_incognito ? profile->GetOffTheRecordProfile()
-                                      : profile);
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/launchable.h b/chrome/browser/chromeos/launchable.h
deleted file mode 100644
index 70514de..0000000
--- a/chrome/browser/chromeos/launchable.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2017 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_BROWSER_CHROMEOS_LAUNCHABLE_H_
-#define CHROME_BROWSER_CHROMEOS_LAUNCHABLE_H_
-
-#include "base/macros.h"
-#include "mash/public/mojom/launchable.mojom.h"
-#include "mojo/public/cpp/bindings/binding_set.h"
-
-namespace chromeos {
-
-class Launchable : public mash::mojom::Launchable {
- public:
-  Launchable();
-  ~Launchable() override;
-
-  void Bind(mash::mojom::LaunchableRequest request);
-
- private:
-  // mash::mojom::Launchable:
-  void Launch(uint32_t what, mash::mojom::LaunchMode how) override;
-
-  void CreateNewWindowImpl(bool is_incognito);
-
-  mojo::BindingSet<mash::mojom::Launchable> bindings_;
-
-  DISALLOW_COPY_AND_ASSIGN(Launchable);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_LAUNCHABLE_H_
diff --git a/chrome/browser/chromeos/login/screenshot_testing/screenshot_tester.cc b/chrome/browser/chromeos/login/screenshot_testing/screenshot_tester.cc
index 7648581..ddeb028 100644
--- a/chrome/browser/chromeos/login/screenshot_testing/screenshot_tester.cc
+++ b/chrome/browser/chromeos/login/screenshot_testing/screenshot_tester.cc
@@ -231,7 +231,7 @@
   base::GetFileSize(image_path, &golden_screenshot_size);
 
   if (golden_screenshot_size == -1) {
-    CHECK(false) << "Can't get golden screenshot size";
+    LOG(FATAL) << "Can't get golden screenshot size";
   }
   scoped_refptr<base::RefCountedBytes> png_data = new base::RefCountedBytes;
   png_data->data().resize(golden_screenshot_size);
diff --git a/chrome/browser/chromeos/login/signin/oauth2_login_manager.cc b/chrome/browser/chromeos/login/signin/oauth2_login_manager.cc
index 1a382a3..2d66c39 100644
--- a/chrome/browser/chromeos/login/signin/oauth2_login_manager.cc
+++ b/chrome/browser/chromeos/login/signin/oauth2_login_manager.cc
@@ -111,7 +111,7 @@
 }
 
 void OAuth2LoginManager::OnRefreshTokenUpdatedForAccount(
-    const AccountInfo& account_info) {
+    const CoreAccountInfo& account_info) {
   VLOG(1) << "OnRefreshTokenUpdatedForAccount";
 
   if (state_ == SESSION_RESTORE_NOT_STARTED)
diff --git a/chrome/browser/chromeos/login/signin/oauth2_login_manager.h b/chrome/browser/chromeos/login/signin/oauth2_login_manager.h
index 887af40..3e6bb37 100644
--- a/chrome/browser/chromeos/login/signin/oauth2_login_manager.h
+++ b/chrome/browser/chromeos/login/signin/oauth2_login_manager.h
@@ -154,7 +154,7 @@
 
   // identity::IdentityManager::Observer implementation:
   void OnRefreshTokenUpdatedForAccount(
-      const AccountInfo& account_info) override;
+      const CoreAccountInfo& account_info) override;
 
   // Signals delegate that authentication is completed, kicks off token fetching
   // process.
diff --git a/chrome/browser/chromeos/net/network_portal_detector_impl_browsertest.cc b/chrome/browser/chromeos/net/network_portal_detector_impl_browsertest.cc
index 25f4068..89da027 100644
--- a/chrome/browser/chromeos/net/network_portal_detector_impl_browsertest.cc
+++ b/chrome/browser/chromeos/net/network_portal_detector_impl_browsertest.cc
@@ -58,7 +58,7 @@
 
 void ErrorCallbackFunction(const std::string& error_name,
                            const std::string& error_message) {
-  CHECK(false) << "Shill Error: " << error_name << " : " << error_message;
+  LOG(FATAL) << "Shill Error: " << error_name << " : " << error_message;
 }
 
 void SetConnected(const std::string& service_path) {
diff --git a/chrome/browser/chromeos/smb_client/smb_file_system.cc b/chrome/browser/chromeos/smb_client/smb_file_system.cc
index 023dbbf6..c9ca802 100644
--- a/chrome/browser/chromeos/smb_client/smb_file_system.cc
+++ b/chrome/browser/chromeos/smb_client/smb_file_system.cc
@@ -699,14 +699,26 @@
     const smbprovider::DirectoryEntryListProto& entries) {
   task_queue_.TaskFinished();
 
-  if (error == smbprovider::ERROR_ACCESS_DENIED) {
-    // Request updated credentials for share, then retry the read directory from
-    // the start.
-    base::OnceClosure retry =
-        base::BindOnce(&SmbFileSystem::StartReadDirectory, AsWeakPtr(),
-                       directory_path, operation_id, std::move(callback));
-    RequestUpdatedCredentials(std::move(retry));
-    return;
+  if (IsRecoverableError(error)) {
+    if (error == smbprovider::ERROR_ACCESS_DENIED) {
+      base::OnceClosure retry =
+          base::BindOnce(&SmbFileSystem::StartReadDirectory, AsWeakPtr(),
+                         directory_path, operation_id, std::move(callback));
+      // Request updated credentials for share, then retry the read directory
+      // from the start.
+      RequestUpdatedCredentials(std::move(retry));
+      return;
+    }
+
+    if (error == smbprovider::ERROR_NOT_FOUND) {
+      // Request updated share path for share, then retry the read directory
+      // from the start.
+      SmbService::StartReadDirIfSuccessfulCallback retry_start_read_dir =
+          base::BindOnce(&SmbFileSystem::RetryStartReadDir, AsWeakPtr(),
+                         directory_path, operation_id, std::move(callback));
+      RequestUpdatedSharePath(std::move(retry_start_read_dir));
+      return;
+    }
   }
 
   int entries_count = 0;
@@ -868,5 +880,26 @@
   return AsWeakPtr();
 }
 
+bool SmbFileSystem::IsRecoverableError(smbprovider::ErrorType error) const {
+  return (error == smbprovider::ERROR_NOT_FOUND) ||
+         (error == smbprovider::ERROR_INVALID_OPERATION) ||
+         (error == smbprovider::ERROR_ACCESS_DENIED);
+}
+
+void SmbFileSystem::RetryStartReadDir(
+    const base::FilePath& directory_path,
+    OperationId operation_id,
+    storage::AsyncFileUtil::ReadDirectoryCallback callback,
+    bool should_retry_start_read_dir) {
+  if (should_retry_start_read_dir) {
+    StartReadDirectory(directory_path, operation_id, std::move(callback));
+  } else {
+    // Run |callback| to terminate StartReadDirectory early.
+    std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND,
+                            storage::AsyncFileUtil::EntryList(),
+                            false /* has_more */);
+  }
+}
+
 }  // namespace smb_client
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/smb_client/smb_file_system.h b/chrome/browser/chromeos/smb_client/smb_file_system.h
index dc7e9be..aafd390 100644
--- a/chrome/browser/chromeos/smb_client/smb_file_system.h
+++ b/chrome/browser/chromeos/smb_client/smb_file_system.h
@@ -349,6 +349,17 @@
   // the OperationId for the newly created Operation.
   OperationId EnqueueTaskAndGetOperationId(SmbTask task);
 
+  // Check if the error can be recovered and handled to continue its original
+  // operation. Returns true if error can be handled.
+  bool IsRecoverableError(smbprovider::ErrorType error) const;
+
+  // Runs the StartReadDirectory if |should_retry_start_read_dir| is true. If
+  // false, |callback| will run instead.
+  void RetryStartReadDir(const base::FilePath& directory_path,
+                         OperationId operation_id,
+                         storage::AsyncFileUtil::ReadDirectoryCallback callback,
+                         bool should_retry_start_read_dir);
+
   const file_system_provider::ProvidedFileSystemInfo file_system_info_;
   // opened_files_ is marked const since is currently unsupported.
   const file_system_provider::OpenedFiles opened_files_;
diff --git a/chrome/browser/chromeos/smb_client/smb_service.cc b/chrome/browser/chromeos/smb_client/smb_service.cc
index 7e0e2c0..0655a59 100644
--- a/chrome/browser/chromeos/smb_client/smb_service.cc
+++ b/chrome/browser/chromeos/smb_client/smb_service.cc
@@ -437,13 +437,6 @@
 void SmbService::OnPremountResponse(const base::FilePath& share_path,
                                     smbprovider::ErrorType error,
                                     int32_t mount_id) {
-  const bool allowed_error = (error == smbprovider::ERROR_OK) ||
-                             (error == smbprovider::ERROR_ACCESS_DENIED);
-  if (!allowed_error) {
-    LOG(ERROR) << "Error mounting preconfigured share in smbprovider.";
-    return;
-  }
-
   DCHECK_GE(mount_id, 0);
 
   file_system_provider::MountOptions mount_options;
diff --git a/chrome/browser/extensions/api/identity/identity_api.cc b/chrome/browser/extensions/api/identity/identity_api.cc
index c707116..ce21776 100644
--- a/chrome/browser/extensions/api/identity/identity_api.cc
+++ b/chrome/browser/extensions/api/identity/identity_api.cc
@@ -165,7 +165,7 @@
 }
 
 void IdentityAPI::OnRefreshTokenUpdatedForAccount(
-    const AccountInfo& account_info) {
+    const CoreAccountInfo& account_info) {
   // Refresh tokens are sometimes made available in contexts where
   // AccountTrackerService is not tracking the account in question (one example
   // is SupervisedUserService::InitSync()). Bail out in these cases.
diff --git a/chrome/browser/extensions/api/identity/identity_api.h b/chrome/browser/extensions/api/identity/identity_api.h
index 6c3056e..6f7f860 100644
--- a/chrome/browser/extensions/api/identity/identity_api.h
+++ b/chrome/browser/extensions/api/identity/identity_api.h
@@ -130,7 +130,7 @@
 
   // identity::IdentityManager::Observer:
   void OnRefreshTokenUpdatedForAccount(
-      const AccountInfo& account_info) override;
+      const CoreAccountInfo& account_info) override;
   // NOTE: This class must listen for this callback rather than
   // OnRefreshTokenRemovedForAccount() to obtain the Gaia ID of the removed
   // account.
diff --git a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc
index 5ab9d96..23e11ca 100644
--- a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc
+++ b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc
@@ -546,7 +546,7 @@
 }
 
 void IdentityGetAuthTokenFunction::OnRefreshTokenUpdatedForAccount(
-    const AccountInfo& account_info) {
+    const CoreAccountInfo& account_info) {
   if (account_listening_mode_ != AccountListeningMode::kListeningTokens)
     return;
 
diff --git a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h
index 7e7d519..9107bd8 100644
--- a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h
+++ b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h
@@ -142,7 +142,7 @@
 
   // identity::IdentityManager::Observer implementation:
   void OnRefreshTokenUpdatedForAccount(
-      const AccountInfo& account_info) override;
+      const CoreAccountInfo& account_info) override;
   void OnAccountsInCookieUpdated(
       const identity::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
       const GoogleServiceAuthError& error) override;
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index c2cbc5b..ec30bc8 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -540,7 +540,7 @@
   },
   {
     "name": "disable-cast-streaming-hw-encoding",
-    // "owners": [ "your-team" ],
+    "owners": [ "miu" ],
     "expiry_milestone": 76
   },
   {
diff --git a/chrome/browser/media/webrtc/desktop_media_picker.h b/chrome/browser/media/webrtc/desktop_media_picker.h
index 9aed4c7..a698e90 100644
--- a/chrome/browser/media/webrtc/desktop_media_picker.h
+++ b/chrome/browser/media/webrtc/desktop_media_picker.h
@@ -47,6 +47,8 @@
     base::string16 target_name;
     // Whether audio capture should be shown as an option in the picker.
     bool request_audio = false;
+    // Whether audio capture option should be approved by default if shown.
+    bool approve_audio_by_default = true;
   };
 
   // Creates default implementation of DesktopMediaPicker for the current
diff --git a/chrome/browser/media/webrtc/display_media_access_handler.cc b/chrome/browser/media/webrtc/display_media_access_handler.cc
index 4b77ae7..1f6b4fe8 100644
--- a/chrome/browser/media/webrtc/display_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/display_media_access_handler.cc
@@ -164,6 +164,7 @@
   picker_params.target_name = picker_params.app_name;
   picker_params.request_audio =
       pending_request.request.audio_type == blink::MEDIA_DISPLAY_AUDIO_CAPTURE;
+  picker_params.approve_audio_by_default = false;
   pending_request.picker->Show(picker_params, std::move(source_lists),
                                done_callback);
 }
diff --git a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base.cc b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base.cc
index 28e802a..f8a2489 100644
--- a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base.cc
+++ b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base.cc
@@ -17,10 +17,10 @@
 #include "chrome/browser/page_load_metrics/page_load_metrics_util.h"
 #include "chrome/browser/previews/previews_ui_tab_helper.h"
 #include "chrome/common/page_load_metrics/page_load_timing.h"
+#include "components/data_reduction_proxy/content/browser/data_reduction_proxy_page_load_timing.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h"
-#include "components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
 #include "components/data_reduction_proxy/proto/pageload_metrics.pb.h"
 #include "components/previews/content/previews_user_data.h"
diff --git a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base_unittest.cc b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base_unittest.cc
index 3ec4736..59d41aa 100644
--- a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_base_unittest.cc
@@ -21,9 +21,9 @@
 #include "chrome/common/page_load_metrics/page_load_timing.h"
 #include "chrome/common/page_load_metrics/test/page_load_metrics_test_util.h"
 #include "chrome/test/base/testing_browser_process.h"
+#include "components/data_reduction_proxy/content/browser/data_reduction_proxy_page_load_timing.h"
 #include "components/data_reduction_proxy/content/browser/data_reduction_proxy_pingback_client_impl.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
-#include "components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
 #include "components/previews/content/previews_user_data.h"
 #include "content/public/test/web_contents_tester.h"
diff --git a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.h b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.h
index 85a7e2b..290a0d6 100644
--- a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.h
+++ b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_test_utils.h
@@ -17,9 +17,9 @@
 #include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h"
 #include "chrome/common/page_load_metrics/page_load_timing.h"
 #include "chrome/common/page_load_metrics/test/page_load_metrics_test_util.h"
+#include "components/data_reduction_proxy/content/browser/data_reduction_proxy_page_load_timing.h"
 #include "components/data_reduction_proxy/content/browser/data_reduction_proxy_pingback_client_impl.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
-#include "components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h"
 #include "components/previews/content/previews_user_data.h"
 #include "net/nqe/effective_connection_type.h"
 #include "third_party/blink/public/platform/web_input_event.h"
diff --git a/chrome/browser/password_manager/password_manager_browsertest.cc b/chrome/browser/password_manager/password_manager_browsertest.cc
index 8081368..7236ae8 100644
--- a/chrome/browser/password_manager/password_manager_browsertest.cc
+++ b/chrome/browser/password_manager/password_manager_browsertest.cc
@@ -2268,15 +2268,22 @@
   std::string username_field;
   std::string password_field;
 
-  // Verify username has been autofilled
   ASSERT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractString(
       RenderFrameHost(), "sendMessage('get_username');", &username_field));
-  EXPECT_EQ("temp", username_field);
 
-  // Verify password has been autofilled
   ASSERT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractString(
       RenderFrameHost(), "sendMessage('get_password');", &password_field));
-  EXPECT_EQ("pa55w0rd", password_field);
+
+  // Verify username and password have only been autofilled if FOAS on HTTP is
+  // not active.
+  if (base::FeatureList::IsEnabled(
+          password_manager::features::kFillOnAccountSelectHttp)) {
+    EXPECT_TRUE(username_field.empty());
+    EXPECT_TRUE(password_field.empty());
+  } else {
+    EXPECT_EQ("temp", username_field);
+    EXPECT_EQ("pa55w0rd", password_field);
+  }
 }
 
 // Check that a username and password are not filled in forms in iframes
diff --git a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
index 1e0834a..dc73d30 100644
--- a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
+++ b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
@@ -1829,6 +1829,7 @@
 
   content::WebContents* active_web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
+
   ASSERT_NE(nullptr, active_web_contents);
 
   ASSERT_TRUE(content::ExecuteScript(active_web_contents, "video.play();"));
@@ -2570,3 +2571,21 @@
             content::TitleWatcher(active_web_contents, expected_title)
                 .WaitAndGetTitle());
 }
+
+// Tests that exiting Picture-in-Picture when the video has no source fires the
+// event and resolves the callback.
+IN_PROC_BROWSER_TEST_F(PictureInPictureWindowControllerBrowserTest,
+                       ExitFireEventAndCallbackWhenNoSource) {
+  LoadTabAndEnterPictureInPicture(browser());
+
+  content::WebContents* active_web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(content::ExecuteScript(active_web_contents,
+                                     "video.src=''; exitPictureInPicture();"));
+
+  // 'left' is sent when the first video leaves Picture-in-Picture.
+  base::string16 expected_title = base::ASCIIToUTF16("left");
+  EXPECT_EQ(expected_title,
+            content::TitleWatcher(active_web_contents, expected_title)
+                .WaitAndGetTitle());
+}
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service.cc b/chrome/browser/policy/cloud/user_policy_signin_service.cc
index e111707..fccda2b 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service.cc
@@ -139,7 +139,7 @@
 }
 
 void UserPolicySigninService::OnRefreshTokenUpdatedForAccount(
-    const AccountInfo& account_info) {
+    const CoreAccountInfo& account_info) {
   // Ignore OAuth tokens or those for any account but the primary one.
   if (account_info.account_id != identity_manager()->GetPrimaryAccountId())
     return;
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service.h b/chrome/browser/policy/cloud/user_policy_signin_service.h
index 26b1765..264b44f 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service.h
+++ b/chrome/browser/policy/cloud/user_policy_signin_service.h
@@ -63,7 +63,7 @@
   // UserPolicySigninServiceBase is already an observer of IdentityManager.
   void OnPrimaryAccountSet(const CoreAccountInfo& account_info) override;
   void OnRefreshTokenUpdatedForAccount(
-      const AccountInfo& account_info) override;
+      const CoreAccountInfo& account_info) override;
 
   // CloudPolicyService::Observer implementation:
   void OnCloudPolicyServiceInitializationCompleted() override;
diff --git a/chrome/browser/safe_browsing/chrome_password_protection_service.cc b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
index 2760db9..a690db0 100644
--- a/chrome/browser/safe_browsing/chrome_password_protection_service.cc
+++ b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
@@ -25,7 +25,6 @@
 #include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/safe_browsing/ui_manager.h"
-#include "chrome/browser/signin/account_tracker_service_factory.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/sync/user_event_service_factory.h"
@@ -51,7 +50,6 @@
 #include "components/safe_browsing/triggers/trigger_throttler.h"
 #include "components/safe_browsing/web_ui/safe_browsing_ui.h"
 #include "components/signin/core/browser/account_info.h"
-#include "components/signin/core/browser/account_tracker_service.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/sync/protocol/user_event_specifics.pb.h"
 #include "components/sync/user_events/user_event_service.h"
diff --git a/chrome/browser/safe_browsing/download_protection/check_client_download_request.cc b/chrome/browser/safe_browsing/download_protection/check_client_download_request.cc
index dbea3de..5b0eb7b 100644
--- a/chrome/browser/safe_browsing/download_protection/check_client_download_request.cc
+++ b/chrome/browser/safe_browsing/download_protection/check_client_download_request.cc
@@ -593,6 +593,11 @@
 void CheckClientDownloadRequest::SendRequest() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
+  if (item_->GetState() == download::DownloadItem::CANCELLED) {
+    FinishRequest(DownloadCheckResult::UNKNOWN, REASON_DOWNLOAD_DESTROYED);
+    return;
+  }
+
   auto request = std::make_unique<ClientDownloadRequest>();
   auto population = is_extended_reporting_
                         ? ChromeUserPopulation::EXTENDED_REPORTING
@@ -809,8 +814,7 @@
            << " verdict:" << reason << " result:" << static_cast<int>(result);
   UMA_HISTOGRAM_ENUMERATION("SBClientDownload.CheckDownloadStats", reason,
                             REASON_MAX);
-  if (reason != REASON_DOWNLOAD_DESTROYED)
-    callback_.Run(result);
+  callback_.Run(result);
   item_->RemoveObserver(this);
   service_->RequestFinished(this);
   // DownloadProtectionService::RequestFinished may delete us.
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc b/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
index 5f8ed365..66a4fbe 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
@@ -2187,7 +2187,7 @@
 
   // When download is destroyed, no need to check for client download request
   // result.
-  EXPECT_FALSE(has_result_);
+  EXPECT_TRUE(has_result_);
   EXPECT_FALSE(HasClientDownloadRequest());
 }
 
@@ -2221,7 +2221,7 @@
                           base::Unretained(this)));
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_FALSE(has_result_);
+  EXPECT_TRUE(has_result_);
   EXPECT_FALSE(HasClientDownloadRequest());
 }
 
@@ -2730,4 +2730,37 @@
   EXPECT_EQ(referrer_chain_data->referrer_chain_length(), 3u);
 }
 
+TEST_F(DownloadProtectionServiceTest, DoesNotSendPingForCancelledDownloads) {
+  PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
+                  net::URLRequestStatus::SUCCESS);
+
+  NiceMockDownloadItem item;
+  PrepareBasicDownloadItem(&item, {"http://www.evil.test/a.exe"},  // url_chain
+                           "http://www.google.com/",               // referrer
+                           FILE_PATH_LITERAL("a.tmp"),             // tmp_path
+                           FILE_PATH_LITERAL("a.exe"));            // final_path
+
+  // Mock a cancelled download.
+  EXPECT_CALL(item, GetState())
+      .WillRepeatedly(Return(download::DownloadItem::CANCELLED));
+
+  EXPECT_CALL(*sb_service_->mock_database_manager(),
+              MatchDownloadWhitelistUrl(_))
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
+  EXPECT_CALL(*binary_feature_extractor_.get(),
+              ExtractImageFeatures(
+                  tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _));
+
+  RunLoop run_loop;
+  download_service_->CheckClientDownload(
+      &item,
+      base::BindRepeating(&DownloadProtectionServiceTest::CheckDoneCallback,
+                          base::Unretained(this), run_loop.QuitClosure()));
+  run_loop.Run();
+
+  EXPECT_TRUE(has_result_);
+  EXPECT_FALSE(HasClientDownloadRequest());
+}
+
 }  // namespace safe_browsing
diff --git a/chrome/browser/search/ntp_icon_source.cc b/chrome/browser/search/ntp_icon_source.cc
index 3cf49f7..6987a14 100644
--- a/chrome/browser/search/ntp_icon_source.cc
+++ b/chrome/browser/search/ntp_icon_source.cc
@@ -56,6 +56,8 @@
 
 namespace {
 
+const char kImageFetcherUmaClientName[] = "NtpIconSource";
+
 // The color of the letter drawn for a fallback icon.  Changing this may require
 // changing the algorithm in RenderIconBitmap() that guarantees contrast.
 constexpr SkColor kFallbackIconLetterColor = SK_ColorWHITE;
@@ -430,7 +432,8 @@
           "default."
         policy_exception_justification: "Not implemented."
       })");
-  image_fetcher::ImageFetcherParams params(traffic_annotation);
+  image_fetcher::ImageFetcherParams params(traffic_annotation,
+                                           kImageFetcherUmaClientName);
   params.set_frame_size(
       gfx::Size(request.icon_size_in_pixels, request.icon_size_in_pixels));
   image_fetcher_->FetchImage(
diff --git a/chrome/browser/search/suggestions/image_fetcher_impl_browsertest.cc b/chrome/browser/search/suggestions/image_fetcher_impl_browsertest.cc
index 1eb9ca9..48265fa 100644
--- a/chrome/browser/search/suggestions/image_fetcher_impl_browsertest.cc
+++ b/chrome/browser/search/suggestions/image_fetcher_impl_browsertest.cc
@@ -28,6 +28,7 @@
 
 namespace {
 
+const char kImageFetcherUmaClientName[] = "TestClientName";
 const char kTestImagePath[] = "/image_decoding/droids.png";
 const char kInvalidImagePath[] = "/DOESNOTEXIST";
 
@@ -80,6 +81,8 @@
 
   void FetchImageAndDataHelper(const GURL& image_url) {
     std::unique_ptr<ImageFetcher> image_fetcher_(CreateImageFetcher());
+    image_fetcher::ImageFetcherParams params(TRAFFIC_ANNOTATION_FOR_TESTS,
+                                             kImageFetcherUmaClientName);
 
     base::RunLoop run_loop;
     image_fetcher_->FetchImageAndData(
@@ -88,7 +91,7 @@
                        base::Unretained(this)),
         base::Bind(&ImageFetcherImplBrowserTest::OnImageAvailable,
                    base::Unretained(this), &run_loop),
-        TRAFFIC_ANNOTATION_FOR_TESTS);
+        std::move(params));
     run_loop.Run();
   }
 
diff --git a/chrome/browser/signin/dice_browsertest.cc b/chrome/browser/signin/dice_browsertest.cc
index ae5d13b..6e0128e 100644
--- a/chrome/browser/signin/dice_browsertest.cc
+++ b/chrome/browser/signin/dice_browsertest.cc
@@ -527,7 +527,7 @@
   }
 
   void OnRefreshTokenUpdatedForAccount(
-      const AccountInfo& account_info) override {
+      const CoreAccountInfo& account_info) override {
     if (account_info.account_id == GetMainAccountID()) {
       refresh_token_available_ = true;
       RunClosureIfValid(std::move(refresh_token_available_quit_closure_));
diff --git a/chrome/browser/sync/test/integration/send_tab_to_self_helper.cc b/chrome/browser/sync/test/integration/send_tab_to_self_helper.cc
index d60e2c8..9515e31 100644
--- a/chrome/browser/sync/test/integration/send_tab_to_self_helper.cc
+++ b/chrome/browser/sync/test/integration/send_tab_to_self_helper.cc
@@ -43,7 +43,14 @@
   CheckExitCondition();
 }
 
-void SendTabToSelfUrlChecker::SendTabToSelfModelChanged() {
+void SendTabToSelfUrlChecker::SendTabToSelfEntriesAdded(
+    const std::vector<const send_tab_to_self::SendTabToSelfEntry*>&
+        new_entries) {
+  CheckExitCondition();
+}
+
+void SendTabToSelfUrlChecker::SendTabToSelfEntriesRemoved(
+    const std::vector<std::string>& guids_removed) {
   CheckExitCondition();
 }
 
@@ -99,7 +106,14 @@
   CheckExitCondition();
 }
 
-void SendTabToSelfModelEqualityChecker::SendTabToSelfModelChanged() {
+void SendTabToSelfModelEqualityChecker::SendTabToSelfEntriesAdded(
+    const std::vector<const send_tab_to_self::SendTabToSelfEntry*>&
+        new_entries) {
+  CheckExitCondition();
+}
+
+void SendTabToSelfModelEqualityChecker::SendTabToSelfEntriesRemoved(
+    const std::vector<std::string>& guids_removed) {
   CheckExitCondition();
 }
 
diff --git a/chrome/browser/sync/test/integration/send_tab_to_self_helper.h b/chrome/browser/sync/test/integration/send_tab_to_self_helper.h
index 38130da..4a3764f 100644
--- a/chrome/browser/sync/test/integration/send_tab_to_self_helper.h
+++ b/chrome/browser/sync/test/integration/send_tab_to_self_helper.h
@@ -13,6 +13,7 @@
 #include "url/gurl.h"
 
 namespace send_tab_to_self {
+class SendTabToSelfEntry;
 class SendTabToSelfSyncService;
 }  // namespace send_tab_to_self
 
@@ -34,7 +35,11 @@
 
   // SendTabToSelfModelObserver implementation.
   void SendTabToSelfModelLoaded() override;
-  void SendTabToSelfModelChanged() override;
+  void SendTabToSelfEntriesAdded(
+      const std::vector<const send_tab_to_self::SendTabToSelfEntry*>&
+          new_entries) override;
+  void SendTabToSelfEntriesRemoved(
+      const std::vector<std::string>& guids_removed) override;
 
  private:
   const GURL url_;
@@ -60,7 +65,11 @@
 
   // SendTabToSelfModelObserver implementation.
   void SendTabToSelfModelLoaded() override;
-  void SendTabToSelfModelChanged() override;
+  void SendTabToSelfEntriesAdded(
+      const std::vector<const send_tab_to_self::SendTabToSelfEntry*>&
+          new_entries) override;
+  void SendTabToSelfEntriesRemoved(
+      const std::vector<std::string>& guids_removed) override;
 
  private:
   send_tab_to_self::SendTabToSelfSyncService* const service0_;
diff --git a/chrome/browser/sync/test/integration/single_client_sessions_sync_test.cc b/chrome/browser/sync/test/integration/single_client_sessions_sync_test.cc
index a07ab0b..322085c 100644
--- a/chrome/browser/sync/test/integration/single_client_sessions_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_sessions_sync_test.cc
@@ -9,7 +9,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/sessions/session_service.h"
 #include "chrome/browser/sessions/session_tab_helper.h"
-#include "chrome/browser/signin/gaia_cookie_manager_service_factory.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/session_sync_service_factory.h"
 #include "chrome/browser/sync/sessions/sync_sessions_router_tab_helper.h"
 #include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
@@ -24,7 +24,6 @@
 #include "components/history/core/browser/history_types.h"
 #include "components/sessions/core/session_types.h"
 #include "components/signin/core/browser/account_info.h"
-#include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/sync/base/time.h"
 #include "components/sync/protocol/proto_value_conversions.h"
 #include "components/sync/test/fake_server/sessions_hierarchy.h"
@@ -33,6 +32,7 @@
 #include "components/sync_sessions/session_sync_test_helper.h"
 #include "components/sync_sessions/synced_session_tracker.h"
 #include "google_apis/gaia/gaia_auth_util.h"
+#include "services/identity/public/cpp/identity_test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/mojo/window_open_disposition.mojom.h"
@@ -708,10 +708,11 @@
                          /*expected_inclusive_lower_bound=*/1);
   }
 
-  // Avoid interferences from actual GaiaCookieManagerService trying to fetch
-  // gaia account information, which would exercise
+  // Avoid interferences from actual IdentityManager trying to fetch gaia
+  // account information, which would exercise
   // ProfileSyncService::OnAccountsInCookieUpdated().
-  GaiaCookieManagerServiceFactory::GetForProfile(GetProfile(0))->CancelAll();
+  identity::CancelAllOngoingGaiaCookieOperations(
+      IdentityManagerFactory::GetForProfile(GetProfile(0)));
 
   // Trigger a cookie jar change (user signing in to content area).
   // Updating the cookie jar has to travel to the sync engine. It is possible
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 958db1d..7294098 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1273,7 +1273,6 @@
       "//components/web_modal",
       "//components/zoom",
       "//device/bluetooth",
-      "//mash/public/mojom",
       "//services/device/public/cpp:device_features",
       "//services/device/public/mojom",
       "//services/identity/public/cpp",
diff --git a/chrome/browser/ui/ash/DEPS b/chrome/browser/ui/ash/DEPS
index b639e15..36c62ca 100644
--- a/chrome/browser/ui/ash/DEPS
+++ b/chrome/browser/ui/ash/DEPS
@@ -8,6 +8,7 @@
 specific_include_rules = {
   ".*test.*": [
    "!ash",
+   "+ash/components/shortcut_viewer/public",
    "+ash/public",
   ],
   # AshShellInit supports classic (non-mash) mode so allow ash/ includes.
diff --git a/chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_metadata_unittest.cc b/chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_metadata_unittest.cc
index d5b7705..efa73d1 100644
--- a/chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_metadata_unittest.cc
+++ b/chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_metadata_unittest.cc
@@ -22,7 +22,7 @@
 // The total number of Ash accelerators.
 constexpr int kAshAcceleratorsTotalNum = 101;
 // The hash of Ash accelerators.
-constexpr char kAshAcceleratorsHash[] = "56daf349457b3dfaf54b804dbc6779a1";
+constexpr char kAshAcceleratorsHash[] = "8163b19db508bf57e9bf4f388b529ef3";
 #if defined(GOOGLE_CHROME_BUILD)
 // Internal builds add an extra accelerator for the Feedback app.
 // The total number of Chrome accelerators (available on Chrome OS).
@@ -80,7 +80,7 @@
 };
 
 std::string HashAshAcceleratorData(
-    const std::vector<ash::AcceleratorData> accelerators) {
+    const std::vector<ash::AcceleratorData>& accelerators) {
   base::MD5Context context;
   base::MD5Init(&context);
   for (const auto& accelerator : accelerators)
@@ -92,7 +92,7 @@
 }
 
 std::string HashChromeAcceleratorMapping(
-    const std::vector<AcceleratorMapping> accelerators) {
+    const std::vector<AcceleratorMapping>& accelerators) {
   base::MD5Context context;
   base::MD5Init(&context);
   for (const auto& accelerator : accelerators)
diff --git a/chrome/browser/ui/ash/login_screen_client.cc b/chrome/browser/ui/ash/login_screen_client.cc
index 1c56bb9e..eb9a20e5 100644
--- a/chrome/browser/ui/ash/login_screen_client.cc
+++ b/chrome/browser/ui/ash/login_screen_client.cc
@@ -287,3 +287,11 @@
   login_screen_->SetPublicSessionKeyboardLayouts(account_id, locale,
                                                  std::move(result));
 }
+
+void LoginScreenClient::OnUserActivity() {
+  if (chromeos::LoginDisplayHost::default_host()) {
+    chromeos::LoginDisplayHost::default_host()
+        ->GetExistingUserController()
+        ->ResetAutoLoginTimer();
+  }
+}
diff --git a/chrome/browser/ui/ash/login_screen_client.h b/chrome/browser/ui/ash/login_screen_client.h
index ac6f7a8..db64135 100644
--- a/chrome/browser/ui/ash/login_screen_client.h
+++ b/chrome/browser/ui/ash/login_screen_client.h
@@ -114,6 +114,7 @@
   void ShowResetScreen() override;
   void ShowAccountAccessHelpApp() override;
   void OnFocusLeavingSystemTray(bool reverse) override;
+  void OnUserActivity() override;
 
  private:
   void SetPublicSessionKeyboardLayout(
diff --git a/chrome/browser/ui/ash/shortcut_viewer_browsertest.cc b/chrome/browser/ui/ash/shortcut_viewer_browsertest.cc
new file mode 100644
index 0000000..027a835
--- /dev/null
+++ b/chrome/browser/ui/ash/shortcut_viewer_browsertest.cc
@@ -0,0 +1,55 @@
+// Copyright 2019 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/components/shortcut_viewer/public/mojom/shortcut_viewer.mojom.h"
+#include "base/command_line.h"
+#include "base/time/time.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/common/service_manager_connection.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "services/ws/common/switches.h"
+#include "services/ws/public/mojom/constants.mojom.h"
+#include "services/ws/public/mojom/window_server_test.mojom-test-utils.h"
+#include "services/ws/public/mojom/window_server_test.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/compositor/compositor_switches.h"
+
+class ShortcutViewerBrowserTest : public InProcessBrowserTest {
+ public:
+  ShortcutViewerBrowserTest() = default;
+  ~ShortcutViewerBrowserTest() override = default;
+
+  // InProcessBrowserTest:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitch(ws::switches::kUseTestConfig);
+    // This test ensures a paint happens, which requires pixel output.
+    command_line->AppendSwitch(switches::kEnablePixelOutputInTests);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ShortcutViewerBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(ShortcutViewerBrowserTest, Paints) {
+  // Start up shortcut_viewer.
+  service_manager::Connector* connector =
+      content::ServiceManagerConnection::GetForProcess()->GetConnector();
+  shortcut_viewer::mojom::ShortcutViewerPtr shortcut_viewer;
+  connector->BindInterface(service_manager::ServiceFilter::ByName(
+                               shortcut_viewer::mojom::kServiceName),
+                           mojo::MakeRequest(&shortcut_viewer));
+  shortcut_viewer->Toggle(base::TimeTicks::Now());
+
+  // Connect to WindowService and verify shortcut_viewer painted.
+  ws::mojom::WindowServerTestPtr test_interface;
+  connector->BindInterface(
+      service_manager::ServiceFilter::ByName(ws::mojom::kServiceName),
+      mojo::MakeRequest(&test_interface));
+
+  bool success = false;
+  ws::mojom::WindowServerTestAsyncWaiter wait_for(test_interface.get());
+  wait_for.EnsureClientHasDrawnWindow(shortcut_viewer::mojom::kServiceName,
+                                      &success);
+  EXPECT_TRUE(success);
+}
diff --git a/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.h b/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.h
index 86415e0..1ec4c26 100644
--- a/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.h
+++ b/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.h
@@ -65,6 +65,8 @@
   friend class content::WebContentsUserData<
       LocalCardMigrationBubbleControllerImpl>;
 
+  friend class LocalCardMigrationBrowserTest;
+
   void ShowBubbleImplementation();
 
   // Update the visibility and toggled state of the Omnibox save card icon.
diff --git a/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_cocoa.mm b/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_cocoa.mm
index a111111..e89714e 100644
--- a/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_cocoa.mm
+++ b/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_cocoa.mm
@@ -20,11 +20,8 @@
     const DoneCallback& done_callback) {
   controller_.reset([[DesktopMediaPickerController alloc]
       initWithSourceLists:std::move(source_lists)
-                   parent:params.parent.GetNativeNSWindow()
                  callback:done_callback
-                  appName:params.app_name
-               targetName:params.target_name
-             requestAudio:params.request_audio]);
+                   params:params]);
   [controller_ showWindow:nil];
 }
 
diff --git a/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_controller.h b/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_controller.h
index 9e52ee7..6ad7c93 100644
--- a/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_controller.h
+++ b/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_controller.h
@@ -69,17 +69,14 @@
 // Designated initializer.
 // To show the dialog, use |NSWindowController|'s |showWindow:|.
 // |callback| will be called to report the user's selection.
-// |appName| will be used to format the dialog's title and the label, where it
-// appears as the initiator of the request.
-// |targetName| will be used to format the dialog's label and appear as the
-// consumer of the requested stream.
+// |params| will be used to customize the dialog. |params.app_name| will be used
+// to format the dialog's title and the label, where it appears as the initiator
+// of the request. |params.target_name| will be used to format the dialog's
+// label and appear as the consumer of the requested stream.
 - (id)initWithSourceLists:
           (std::vector<std::unique_ptr<DesktopMediaList>>)sourceLists
-                   parent:(NSWindow*)parent
                  callback:(const DesktopMediaPicker::DoneCallback&)callback
-                  appName:(const base::string16&)appName
-               targetName:(const base::string16&)targetName
-             requestAudio:(bool)requestAudio;
+                   params:(const DesktopMediaPicker::Params&)params;
 
 @end
 
diff --git a/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_controller.mm b/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_controller.mm
index b4864da..8b3dcd3 100644
--- a/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_controller.mm
+++ b/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_controller.mm
@@ -63,9 +63,7 @@
 @interface DesktopMediaPickerController ()
 
 // Populate the window with controls and views.
-- (void)initializeContentsWithAppName:(const base::string16&)appName
-                           targetName:(const base::string16&)targetName
-                         requestAudio:(bool)requestAudio;
+- (void)initializeContentsWithParams:(const DesktopMediaPicker::Params&)params;
 
 // Add |NSSegmentControl| for source type switch.
 - (void)createTypeButtonAtOrigin:(NSPoint)origin;
@@ -113,11 +111,8 @@
 
 - (id)initWithSourceLists:
           (std::vector<std::unique_ptr<DesktopMediaList>>)sourceLists
-                   parent:(NSWindow*)parent
                  callback:(const DesktopMediaPicker::DoneCallback&)callback
-                  appName:(const base::string16&)appName
-               targetName:(const base::string16&)targetName
-             requestAudio:(bool)requestAudio {
+                   params:(const DesktopMediaPicker::Params&)params {
   const NSUInteger kStyleMask =
       NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask;
   base::scoped_nsobject<NSWindow> window(
@@ -125,8 +120,8 @@
                                   styleMask:kStyleMask
                                     backing:NSBackingStoreBuffered
                                       defer:NO]);
-
   if ((self = [super initWithWindow:window])) {
+    NSWindow* parent = params.parent.GetNativeNSWindow();
     [parent addChildWindow:window ordered:NSWindowAbove];
     [window setDelegate:self];
 
@@ -150,9 +145,7 @@
       }
     }
 
-    [self initializeContentsWithAppName:appName
-                             targetName:targetName
-                           requestAudio:requestAudio];
+    [self initializeContentsWithParams:params];
     doneCallback_ = callback;
 
     bridge_.reset(new DesktopMediaPickerBridge(self));
@@ -173,9 +166,7 @@
   [super dealloc];
 }
 
-- (void)initializeContentsWithAppName:(const base::string16&)appName
-                           targetName:(const base::string16&)targetName
-                         requestAudio:(bool)requestAudio {
+- (void)initializeContentsWithParams:(const DesktopMediaPicker::Params&)params {
   // Use flipped coordinates to facilitate manual layout.
   base::scoped_nsobject<FlippedView> content(
       [[FlippedView alloc] initWithFrame:NSZeroRect]);
@@ -189,12 +180,13 @@
 
   // Set the dialog's description.
   NSString* descriptionText;
-  if (appName == targetName) {
-    descriptionText = l10n_util::GetNSStringF(
-        IDS_DESKTOP_MEDIA_PICKER_TEXT, appName);
+  if (params.app_name == params.target_name) {
+    descriptionText =
+        l10n_util::GetNSStringF(IDS_DESKTOP_MEDIA_PICKER_TEXT, params.app_name);
   } else {
-    descriptionText = l10n_util::GetNSStringF(
-        IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED, appName, targetName);
+    descriptionText =
+        l10n_util::GetNSStringF(IDS_DESKTOP_MEDIA_PICKER_TEXT_DELEGATED,
+                                params.app_name, params.target_name);
   }
   NSTextField* description =
       [self createTextFieldWithText:descriptionText
@@ -211,10 +203,15 @@
   origin.y +=
       NSHeight([imageBrowserScroll_ frame]) + kDesktopMediaPickerControlSpacing;
 
-  if (requestAudio) {
+  if (params.request_audio) {
     [self createAudioCheckboxAtOrigin:origin];
     origin.y += NSHeight([audioShareCheckbox_ frame]) +
                 kDesktopMediaPickerControlSpacing;
+    if (params.approve_audio_by_default) {
+      [audioShareCheckbox_ setState:NSOnState];
+    } else {
+      [audioShareCheckbox_ setState:NSOffState];
+    }
   }
 
   [self createActionButtonsAtOrigin:origin];
@@ -367,7 +364,6 @@
   [audioShareCheckbox_ setFrameOrigin:origin];
   [audioShareCheckbox_ setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
   [audioShareCheckbox_ setButtonType:NSSwitchButton];
-  [audioShareCheckbox_ setState:NSOnState];
   [audioShareCheckbox_
       setTitle:l10n_util::GetNSString(IDS_DESKTOP_MEDIA_PICKER_AUDIO_SHARE)];
   [audioShareCheckbox_ sizeToFit];
diff --git a/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_controller_unittest.mm b/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_controller_unittest.mm
index a8536cb..8ae4803d 100644
--- a/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/media_picker/desktop_media_picker_controller_unittest.mm
@@ -95,13 +95,15 @@
         base::Bind(&DesktopMediaPickerControllerTest::OnResult,
                    base::Unretained(this));
 
+    DesktopMediaPicker::Params params;
+    params.app_name = base::ASCIIToUTF16("Screenshare Test");
+    params.target_name = base::ASCIIToUTF16("https://foo.com");
+    params.request_audio = true;
+    params.approve_audio_by_default = true;
     controller_.reset([[DesktopMediaPickerController alloc]
         initWithSourceLists:std::move(source_lists)
-                     parent:nil
                    callback:callback
-                    appName:base::ASCIIToUTF16("Screenshare Test")
-                 targetName:base::ASCIIToUTF16("https://foo.com")
-               requestAudio:true]);
+                     params:params]);
   }
 
   void TearDown() override {
diff --git a/chrome/browser/ui/views/autofill/local_card_migration_browsertest.cc b/chrome/browser/ui/views/autofill/local_card_migration_browsertest.cc
index 639212a..21c21bd3 100644
--- a/chrome/browser/ui/views/autofill/local_card_migration_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/local_card_migration_browsertest.cc
@@ -30,6 +30,7 @@
 #include "chrome/browser/ui/views/autofill/local_card_migration_bubble_views.h"
 #include "chrome/browser/ui/views/autofill/local_card_migration_dialog_view.h"
 #include "chrome/browser/ui/views/autofill/local_card_migration_icon_view.h"
+#include "chrome/browser/ui/views/autofill/migratable_card_view.h"
 #include "chrome/browser/ui/views/autofill/save_card_bubble_views.h"
 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -50,6 +51,7 @@
 #include "components/sync/test/fake_server/fake_server_network_resources.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/test/browser_test_utils.h"
+#include "content/public/test/mock_navigation_handle.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/url_request/test_url_fetcher_factory.h"
@@ -78,6 +80,11 @@
 constexpr char kURLGetUploadDetailsRequest[] =
     "https://payments.google.com/payments/apis/chromepaymentsservice/"
     "getdetailsforsavecard";
+constexpr char kURLMigrateCardRequest[] =
+    "https://payments.google.com/payments/apis-secure/chromepaymentsservice/"
+    "migratecards"
+    "?s7e_suffix=chromewallet";
+
 constexpr char kResponseGetUploadDetailsSuccess[] =
     "{\"legal_message\":{\"line\":[{\"template\":\"Legal message template with "
     "link: "
@@ -87,11 +94,18 @@
     "{\"error\":{\"code\":\"FAILED_PRECONDITION\",\"user_error_message\":\"An "
     "unexpected error has occurred. Please try again later.\"}}";
 
+constexpr char kResponseMigrateCardSuccess[] =
+    "{\"save_result\":[{\"unique_id\":\"0\", \"status\":\"SUCCESS\"}, "
+    "{\"unique_id\":\"1\", \"status\":\"SUCCESS\"}], "
+    "\"value_prop_display_text\":\"example message.\"}";
+
 constexpr char kCreditCardFormURL[] =
     "/credit_card_upload_form_address_and_cc.html";
 
 constexpr char kFirstCardNumber[] = "5428424047572420";   // Mastercard
 constexpr char kSecondCardNumber[] = "4782187095085933";  // Visa
+constexpr char kThirdCardNumber[] = "4111111111111111";   // Visa
+constexpr char kInvalidCardNumber[] = "4444444444444444";
 
 constexpr double kFakeGeolocationLatitude = 1.23;
 constexpr double kFakeGeolocationLongitude = 4.56;
@@ -157,7 +171,7 @@
         ->set_url_loader_factory_for_testing(test_shared_loader_factory_);
 
     // Set up this class as the ObserverForTest implementation.
-    LocalCardMigrationManager* local_card_migration_manager =
+    local_card_migration_manager_ =
         ContentAutofillDriver::GetForRenderFrameHost(
             GetActiveWebContents()->GetMainFrame())
             ->autofill_manager()
@@ -165,7 +179,8 @@
             ->GetFormDataImporter()
             ->local_card_migration_manager_.get();
 
-    local_card_migration_manager->SetEventObserverForTesting(this);
+    local_card_migration_manager_->SetEventObserverForTesting(this);
+    personal_data_ = local_card_migration_manager_->personal_data_manager_;
 
     // Set up the fake geolocation data.
     geolocation_overrider_ =
@@ -184,6 +199,7 @@
         features::kAutofillCreditCardLocalCardMigration);
     ASSERT_TRUE(harness_->SetupSync());
     SetUploadDetailsRpcPaymentsAccepts();
+    SetUpMigrateCardsRpcPaymentsAccepts();
   }
 
   void NavigateTo(const std::string& file_path) {
@@ -213,16 +229,21 @@
       event_waiter_->OnEvent(DialogEvent::RECEIVED_MIGRATE_CARDS_RESPONSE);
   }
 
-  void SaveLocalCard(std::string card_number) {
+  CreditCard SaveLocalCard(std::string card_number,
+                           bool set_as_expired_card = false) {
     CreditCard local_card;
     test::SetCreditCardInfo(&local_card, "John Smith", card_number.c_str(),
-                            "12", test::NextYear().c_str(), "1");
+                            "12",
+                            set_as_expired_card ? test::LastYear().c_str()
+                                                : test::NextYear().c_str(),
+                            "1");
     local_card.set_guid("00000000-0000-0000-0000-" + card_number.substr(0, 12));
     local_card.set_record_type(CreditCard::LOCAL_CARD);
     AddTestCreditCard(browser(), local_card);
+    return local_card;
   }
 
-  void SaveServerCard(std::string card_number) {
+  CreditCard SaveServerCard(std::string card_number) {
     CreditCard server_card;
     test::SetCreditCardInfo(&server_card, "John Smith", card_number.c_str(),
                             "12", test::NextYear().c_str(), "1");
@@ -231,6 +252,7 @@
     server_card.set_record_type(CreditCard::FULL_SERVER_CARD);
     server_card.set_server_id("full_id_" + card_number);
     AddTestServerCreditCard(browser(), server_card);
+    return server_card;
   }
 
   void UseCardAndWaitForMigrationOffer(std::string card_number) {
@@ -242,6 +264,13 @@
     WaitForObservedEvent();
   }
 
+  void ClickOnSaveButtonAndWaitForMigrationResults() {
+    ResetEventWaiterForSequence({DialogEvent::SENT_MIGRATE_CARDS_REQUEST,
+                                 DialogEvent::RECEIVED_MIGRATE_CARDS_RESPONSE});
+    ClickOnOkButton(GetLocalCardMigrationMainDialogView());
+    WaitForObservedEvent();
+  }
+
   void FillAndSubmitFormWithCard(std::string card_number) {
     NavigateTo(kCreditCardFormURL);
     content::WebContents* web_contents = GetActiveWebContents();
@@ -272,6 +301,11 @@
                                            kResponseGetUploadDetailsFailure);
   }
 
+  void SetUpMigrateCardsRpcPaymentsAccepts() {
+    test_url_loader_factory()->AddResponse(kURLMigrateCardRequest,
+                                           kResponseMigrateCardSuccess);
+  }
+
   void ClickOnView(views::View* view) {
     DCHECK(view);
     ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
@@ -397,10 +431,16 @@
     return &test_url_loader_factory_;
   }
 
+  void WaitForCardDeletion() { WaitForPersonalDataChange(browser()); }
+
   std::unique_ptr<
       base::CallbackList<void(content::BrowserContext*)>::Subscription>
       will_create_browser_context_services_subscription_;
 
+  LocalCardMigrationManager* local_card_migration_manager_;
+
+  PersonalDataManager* personal_data_;
+
   base::test::ScopedFeatureList scoped_feature_list_;
 
   std::unique_ptr<ProfileSyncServiceHarness> harness_;
@@ -465,6 +505,34 @@
       "Autofill.LocalCardMigrationBubbleOffer.FirstShow", 0);
 }
 
+// Ensures that the intermediate migration bubble is shown after reusing
+// a saved server card, if there is at least one card to migrate.
+IN_PROC_BROWSER_TEST_F(
+    LocalCardMigrationBrowserTest,
+    ReusingServerCardWithMigratableLocalCardShowIntermediateMigrationOffer) {
+  base::HistogramTester histogram_tester;
+
+  SaveServerCard(kFirstCardNumber);
+  SaveLocalCard(kSecondCardNumber);
+  UseCardAndWaitForMigrationOffer(kFirstCardNumber);
+
+  // The intermediate migration bubble should show.
+  EXPECT_TRUE(
+      FindViewInDialogById(DialogViewId::MAIN_CONTENT_VIEW_MIGRATION_BUBBLE,
+                           GetLocalCardMigrationOfferBubbleViews())
+          ->visible());
+  // Metrics
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Autofill.LocalCardMigrationBubbleOffer.FirstShow"),
+      ElementsAre(
+          Bucket(AutofillMetrics::LOCAL_CARD_MIGRATION_BUBBLE_REQUESTED, 1),
+          Bucket(AutofillMetrics::LOCAL_CARD_MIGRATION_BUBBLE_SHOWN, 1)));
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.LocalCardMigrationOrigin.UseOfServerCard",
+      AutofillMetrics::INTERMEDIATE_BUBBLE_SHOWN, 1);
+}
+
 // Ensures that the intermediate migration bubble is not shown after reusing
 // a previously saved local card, if there are no other cards to migrate.
 IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
@@ -593,6 +661,32 @@
       AutofillMetrics::LOCAL_CARD_MIGRATION_DIALOG_SHOWN, 1);
 }
 
+// Ensures that the migration dialog contains all the valid card stored in
+// Chrome browser local storage.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       DialogContainsAllValidMigratableCard) {
+  base::HistogramTester histogram_tester;
+
+  CreditCard first_card = SaveLocalCard(kFirstCardNumber);
+  CreditCard second_card = SaveLocalCard(kSecondCardNumber);
+  SaveLocalCard(kThirdCardNumber, /*set_as_expired_card=*/true);
+  SaveLocalCard(kInvalidCardNumber);
+  UseCardAndWaitForMigrationOffer(kFirstCardNumber);
+  // Click the [Continue] button.
+  ClickOnOkButton(GetLocalCardMigrationOfferBubbleViews());
+
+  views::View* card_list_view = GetCardListView();
+  EXPECT_TRUE(card_list_view->visible());
+  EXPECT_EQ(card_list_view->child_count(), 2);
+  // Cards will be added to database in a reversed order.
+  EXPECT_EQ(static_cast<MigratableCardView*>(card_list_view->child_at(0))
+                ->GetNetworkAndLastFourDigits(),
+            second_card.NetworkAndLastFourDigits());
+  EXPECT_EQ(static_cast<MigratableCardView*>(card_list_view->child_at(1))
+                ->GetNetworkAndLastFourDigits(),
+            first_card.NetworkAndLastFourDigits());
+}
+
 // Ensures that rejecting the main migration dialog closes the dialog.
 IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
                        ClickingCancelClosesDialog) {
@@ -632,8 +726,7 @@
   // Click the [Continue] button in the bubble.
   ClickOnOkButton(GetLocalCardMigrationOfferBubbleViews());
   // Click the [Save] button in the dialog.
-  // TODO(crbug.com/897998): Add Payments Rpc setup and event waiter.
-  ClickOnOkButton(GetLocalCardMigrationMainDialogView());
+  ClickOnSaveButtonAndWaitForMigrationResults();
 
   // No dialog should be showing.
   EXPECT_EQ(nullptr, GetLocalCardMigrationMainDialogView());
@@ -651,16 +744,27 @@
       1);
 }
 
-// TODO(crbug.com/897998): Add these following tests.
-// 1) Reusing a server card shows the intermediate bubble.
-// 2) All valid cards are visible in the migration offer view.
-// 3) Local cards should get deleted after migration.
-// 4) Expired and invalid cards should not be shown.
-// 5) When user navigates away after five seconds, the bubble disappears.
-// 6) When user navigates away after five seconds, the dialog should stay.
-// 7) When user clicks legal message links, browser should show a pop-up window.
-// 8) Simulate checkboxes to ensure
-//    LocalCardMigrationDialogUserSelectionPercentage is logged correctly.
-// 9) Ensure LocalCardMigrationDialogActiveDuration is logged correctly.
+// Ensures local cards will be deleted from browser local storage after being
+// successfully migrated.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       DeleteSuccessfullyMigratedCardsFromLocal) {
+  base::HistogramTester histogram_tester;
+
+  SaveLocalCard(kFirstCardNumber);
+  SaveLocalCard(kSecondCardNumber);
+  UseCardAndWaitForMigrationOffer(kFirstCardNumber);
+  // Click the [Continue] button in the bubble.
+  ClickOnOkButton(GetLocalCardMigrationOfferBubbleViews());
+  // Click the [Save] button in the dialog.
+  ClickOnSaveButtonAndWaitForMigrationResults();
+  WaitForCardDeletion();
+
+  EXPECT_EQ(nullptr, personal_data_->GetCreditCardByNumber(kFirstCardNumber));
+  EXPECT_EQ(nullptr, personal_data_->GetCreditCardByNumber(kSecondCardNumber));
+}
+
+// TODO(crbug.com/897998):
+// - Update test set-up and add navagation tests.
+// - Add more tests for feedback dialog.
 
 }  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/migratable_card_view.cc b/chrome/browser/ui/views/autofill/migratable_card_view.cc
index bd89dde..3fdd34f 100644
--- a/chrome/browser/ui/views/autofill/migratable_card_view.cc
+++ b/chrome/browser/ui/views/autofill/migratable_card_view.cc
@@ -81,6 +81,10 @@
   return migratable_credit_card_.credit_card().guid();
 }
 
+const base::string16 MigratableCardView::GetNetworkAndLastFourDigits() const {
+  return migratable_credit_card_.credit_card().NetworkAndLastFourDigits();
+}
+
 std::unique_ptr<views::View>
 MigratableCardView::GetMigratableCardDescriptionView(
     const MigratableCreditCard& migratable_credit_card,
diff --git a/chrome/browser/ui/views/autofill/migratable_card_view.h b/chrome/browser/ui/views/autofill/migratable_card_view.h
index a3fb3f4..492d60d 100644
--- a/chrome/browser/ui/views/autofill/migratable_card_view.h
+++ b/chrome/browser/ui/views/autofill/migratable_card_view.h
@@ -36,6 +36,7 @@
 
   bool IsSelected();
   std::string GetGuid();
+  const base::string16 GetNetworkAndLastFourDigits() const;
 
   std::unique_ptr<views::View> GetMigratableCardDescriptionView(
       const MigratableCreditCard& migratable_credit_card,
diff --git a/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc b/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc
index 930b65c..9ea8576 100644
--- a/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc
+++ b/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc
@@ -198,7 +198,7 @@
   if (params.request_audio) {
     audio_share_checkbox_ = new views::Checkbox(
         l10n_util::GetStringUTF16(IDS_DESKTOP_MEDIA_PICKER_AUDIO_SHARE));
-    audio_share_checkbox_->SetChecked(true);
+    audio_share_checkbox_->SetChecked(params.approve_audio_by_default);
   }
 
   // Focus on the first non-null media_list.
diff --git a/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc b/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc
index 8eaa8a7..40186ad 100644
--- a/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc
+++ b/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc
@@ -169,9 +169,14 @@
 
 bool RemoteTextInputClient::IsTextEditCommandEnabled(
     ui::TextEditCommand command) const {
-  // TODO(moshayedi): crbug.com/631527.
-  NOTIMPLEMENTED_LOG_ONCE();
-  return false;
+  if (!details_->data->edit_command_enabled.has_value())
+    return false;
+
+  const size_t index = static_cast<size_t>(command);
+  if (index >= details_->data->edit_command_enabled->size())
+    return false;
+
+  return details_->data->edit_command_enabled->at(index);
 }
 
 void RemoteTextInputClient::SetTextEditCommandForNextKeyEvent(
diff --git a/chrome/browser/ui/views/infobars/infobar_view.cc b/chrome/browser/ui/views/infobars/infobar_view.cc
index ff143fc..b81c7dd 100644
--- a/chrome/browser/ui/views/infobars/infobar_view.cc
+++ b/chrome/browser/ui/views/infobars/infobar_view.cc
@@ -51,11 +51,11 @@
   kLabel,
   kLink,
 };
-DEFINE_UI_CLASS_PROPERTY_TYPE(LabelType);
+DEFINE_UI_CLASS_PROPERTY_TYPE(LabelType)
 
 namespace {
 
-DEFINE_UI_CLASS_PROPERTY_KEY(LabelType, kLabelType, LabelType::kNone);
+DEFINE_UI_CLASS_PROPERTY_KEY(LabelType, kLabelType, LabelType::kNone)
 
 // IDs of the colors to use for infobar elements.
 constexpr int kInfoBarLabelBackgroundColor = ThemeProperties::COLOR_INFOBAR;
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index f4d32ff..7d77a9b 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -1258,14 +1258,11 @@
   // midst of running but hasn't yet opened the popup, it will be halted.
   // If we fully reverted in this case, we'd lose the cursor/highlight
   // information saved above. Note: popup_model() can be null in tests.
-  //
-  // TODO(tommycli): This seems like it should apply to the Cocoa version of
-  // the Omnibox as well. Investigate moving this into the OmniboxEditModel.
-  if (!model()->user_input_in_progress() && model()->popup_model() &&
-      model()->popup_model()->IsOpen() &&
-      text() != model()->GetPermanentDisplayText()) {
+  OmniboxPopupModel* popup_model = model()->popup_model();
+  if (!model()->user_input_in_progress() && popup_model &&
+      popup_model->IsOpen() && text() != model()->GetPermanentDisplayText()) {
     RevertAll();
-  } else {
+  } else if (!popup_model || popup_model->popup_closes_on_blur()) {
     CloseOmniboxPopup();
   }
 
diff --git a/chrome/browser/ui/views/profiles/profile_chooser_view.cc b/chrome/browser/ui/views/profiles/profile_chooser_view.cc
index 5d19aee..d01dfd2 100644
--- a/chrome/browser/ui/views/profiles/profile_chooser_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_chooser_view.cc
@@ -449,7 +449,7 @@
 }
 
 void ProfileChooserView::OnRefreshTokenUpdatedForAccount(
-    const AccountInfo& account_info) {
+    const CoreAccountInfo& account_info) {
   if (view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT ||
       view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT ||
       view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH) {
diff --git a/chrome/browser/ui/views/profiles/profile_chooser_view.h b/chrome/browser/ui/views/profiles/profile_chooser_view.h
index f0786e1..c94f8b8 100644
--- a/chrome/browser/ui/views/profiles/profile_chooser_view.h
+++ b/chrome/browser/ui/views/profiles/profile_chooser_view.h
@@ -113,7 +113,7 @@
 
   // identity::IdentityManager::Observer overrides.
   void OnRefreshTokenUpdatedForAccount(
-      const AccountInfo& account_info) override;
+      const CoreAccountInfo& account_info) override;
   void OnRefreshTokenRemovedForAccount(const std::string& account_id) override;
 
   static ProfileChooserView* profile_bubble_;
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
index 21904b5..136a766 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
@@ -1482,7 +1482,12 @@
   // GetCursorScreenPoint() needs to be called before Detach() is called as
   // GetCursorScreenPoint() may use the current attached tabstrip to get the
   // touch event position but Detach() sets attached tabstrip to nullptr.
-  const gfx::Point current_screen_point = GetCursorScreenPoint();
+  // On ChromeOS, the gesture state is already cleared and so
+  // GetCursorScreenPoint() will fail to obtain the last touch location.
+  // Therefore it uses the last remembered location instead.
+  const gfx::Point current_screen_point = (event_source_ == EVENT_SOURCE_TOUCH)
+                                              ? last_point_in_screen_
+                                              : GetCursorScreenPoint();
   Detach(DONT_RELEASE_CAPTURE);
   // If we're attaching the dragged tabs to an overview window's tabstrip, the
   // tabstrip should not have focus.
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.h b/chrome/browser/ui/views/tabs/tab_drag_controller.h
index ca5acbb..b5ba003 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.h
@@ -481,7 +481,8 @@
   void ClearTabDraggingInfo();
 
   // Sets |deferred_target_tabstrip_| and updates its corresponding window
-  // property.
+  // property. |location| is the location of the pointer when the deferred
+  // target is set.
   void SetDeferredTargetTabstrip(TabStrip* deferred_target_tabstrip);
 
   DragState current_state_;
diff --git a/chrome/browser/vr/ui_host/vr_ui_host_impl.cc b/chrome/browser/vr/ui_host/vr_ui_host_impl.cc
index 81a96fc..7ae324d 100644
--- a/chrome/browser/vr/ui_host/vr_ui_host_impl.cc
+++ b/chrome/browser/vr/ui_host/vr_ui_host_impl.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/vr/ui_host/vr_ui_host_impl.h"
 
-#include "chrome/browser/permissions/permission_request.h"
+#include "base/task/post_task.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
 #include "chrome/browser/vr/service/browser_xr_runtime.h"
@@ -20,9 +20,14 @@
 
 namespace vr {
 
+namespace {
+static constexpr base::TimeDelta kPermissionPromptTimeout =
+    base::TimeDelta::FromSeconds(5);
+}  // namespace
+
 VRUiHostImpl::VRUiHostImpl(device::mojom::XRDeviceId device_id,
                            device::mojom::XRCompositorHostPtr compositor)
-    : compositor_(std::move(compositor)) {
+    : compositor_(std::move(compositor)), weak_ptr_factory_(this) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DVLOG(1) << __func__;
 
@@ -149,12 +154,29 @@
 
   ui_rendering_thread_->SetVisibleExternalPromptNotification(
       ExternalPromptNotificationType::kPromptGenericPermission);
+
+  is_prompt_showing_in_headset_ = true;
+  current_prompt_sequence_num_++;
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&VRUiHostImpl::RemoveHeadsetNotificationPrompt,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     current_prompt_sequence_num_),
+      kPermissionPromptTimeout);
 }
 
 void VRUiHostImpl::OnBubbleRemoved() {
+  RemoveHeadsetNotificationPrompt(current_prompt_sequence_num_);
+}
+
+void VRUiHostImpl::RemoveHeadsetNotificationPrompt(int prompt_sequence_num) {
+  if (!is_prompt_showing_in_headset_)
+    return;
+  if (prompt_sequence_num != current_prompt_sequence_num_)
+    return;
+  is_prompt_showing_in_headset_ = false;
   ui_rendering_thread_->SetVisibleExternalPromptNotification(
       ExternalPromptNotificationType::kPromptNone);
   ui_rendering_thread_->StopOverlay();
 }
-
 }  // namespace vr
diff --git a/chrome/browser/vr/ui_host/vr_ui_host_impl.h b/chrome/browser/vr/ui_host/vr_ui_host_impl.h
index cd5b56d..06178ce 100644
--- a/chrome/browser/vr/ui_host/vr_ui_host_impl.h
+++ b/chrome/browser/vr/ui_host/vr_ui_host_impl.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_VR_UI_HOST_VR_UI_HOST_IMPL_H_
 
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/threading/thread_checker.h"
 #include "chrome/browser/permissions/permission_request_manager.h"
 #include "chrome/browser/vr/service/browser_xr_runtime.h"
@@ -45,14 +46,21 @@
   void OnBubbleAdded() override;
   void OnBubbleRemoved() override;
 
+  void RemoveHeadsetNotificationPrompt(int prompt_sequence_num);
+
   device::mojom::XRCompositorHostPtr compositor_;
   std::unique_ptr<VRBrowserRendererThreadWin> ui_rendering_thread_;
   device::mojom::VRDisplayInfoPtr info_;
   content::WebContents* web_contents_ = nullptr;
   PermissionRequestManager* permission_request_manager_ = nullptr;
 
+  bool is_prompt_showing_in_headset_ = false;
+  int current_prompt_sequence_num_ = 0;
+
   THREAD_CHECKER(thread_checker_);
 
+  base::WeakPtrFactory<VRUiHostImpl> weak_ptr_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(VRUiHostImpl);
 };
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 149465a..1de1264 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1937,6 +1937,7 @@
         "../browser/ui/ash/network/networking_config_chromeos_browsertest.cc",
         "../browser/ui/ash/screen_orientation_delegate_chromeos_browsertest.cc",
         "../browser/ui/ash/shelf_browsertest.cc",
+        "../browser/ui/ash/shortcut_viewer_browsertest.cc",
         "../browser/ui/ash/system_tray_client_browsertest.cc",
         "../browser/ui/ash/system_tray_tray_cast_browsertest_media_router_chromeos.cc",
         "../browser/ui/ash/tab_scrubber_browsertest.cc",
diff --git a/chrome/test/base/in_process_browser_test.h b/chrome/test/base/in_process_browser_test.h
index 86d4414..da5f677 100644
--- a/chrome/test/base/in_process_browser_test.h
+++ b/chrome/test/base/in_process_browser_test.h
@@ -80,7 +80,7 @@
 // InProcessBrowserTest::SetUpCommandLine(). If a test needs to change the
 // default command line, it can override SetUpDefaultCommandLine(), where it
 // should invoke InProcessBrowserTest::SetUpDefaultCommandLine() to get the
-// default swtiches, and modify them as needed.
+// default switches, and modify them as needed.
 //
 // SetUpOnMainThread() is called just after creating the default browser object
 // and before executing the real test code. It's mainly for setting up things
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index c6944c2..f9c1d1f 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -76,7 +76,6 @@
     'ChromeDriverTest.testEmulateNetworkConditionsSpeed',
     # crbug.com/469947
     'ChromeDriverTest.testTouchPinch',
-    'ChromeDriverTest.testReturningAFunctionInJavascript',
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1367
     'ChromeExtensionsCapabilityTest.testWaitsForExtensionToLoad',
     # TODO: re-enable tests when DevTools supports ScreenOrientation commands.
@@ -84,10 +83,6 @@
     'ChromeDriverAndroidTest.testMultipleScreenOrientationChanges',
     'ChromeDriverAndroidTest.testDeleteScreenOrientationManual',
     'ChromeDriverAndroidTest.testScreenOrientationAcrossMultipleTabs',
-    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1503
-    'ChromeDriverTest.testShadowDomHover',
-    'ChromeDriverTest.testMouseMoveTo',
-    'ChromeDriverTest.testHoverOverElement',
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=833
     'ChromeDriverTest.testAlertOnNewWindow',
 ]
diff --git a/chrome/test/chromedriver/window_commands.cc b/chrome/test/chromedriver/window_commands.cc
index c228ccd..eab685d 100644
--- a/chrome/test/chromedriver/window_commands.cc
+++ b/chrome/test/chromedriver/window_commands.cc
@@ -680,7 +680,7 @@
                             Timeout* timeout) {
   const char kGetPageSource[] =
       "function() {"
-      "  return document.documentElement.outerHTML;"
+      "  return new XMLSerializer().serializeToString(document);"
       "}";
   base::ListValue args;
   return web_view->CallFunction(
diff --git a/chrome/test/data/chromeos/file_manager/video.vtt b/chrome/test/data/chromeos/file_manager/video.vtt
new file mode 100644
index 0000000..8534bd9
--- /dev/null
+++ b/chrome/test/data/chromeos/file_manager/video.vtt
@@ -0,0 +1,4 @@
+WEBVTT
+
+00:01.000 --> 00:01.000
+- Sample vtt.
diff --git a/chrome/utility/BUILD.gn b/chrome/utility/BUILD.gn
index b460f97..854e99a 100644
--- a/chrome/utility/BUILD.gn
+++ b/chrome/utility/BUILD.gn
@@ -151,8 +151,6 @@
     ]
     deps += [
       "//ash",
-      "//ash/components/quick_launch:lib",
-      "//ash/components/quick_launch/public/mojom",
       "//ash/components/shortcut_viewer:lib",
       "//ash/components/shortcut_viewer/public/mojom",
       "//ash/components/tap_visualizer:lib",
diff --git a/chrome/utility/DEPS b/chrome/utility/DEPS
index 27e9516..dfbae0f 100644
--- a/chrome/utility/DEPS
+++ b/chrome/utility/DEPS
@@ -56,8 +56,6 @@
 specific_include_rules = {
   "mash_service_factory.cc": [
     "+ash/ash_service.h",
-    "+ash/components/quick_launch/public",
-    "+ash/components/quick_launch/quick_launch_application.h",
     "+ash/components/shortcut_viewer/public",
     "+ash/components/shortcut_viewer/shortcut_viewer_application.h",
     "+ash/components/tap_visualizer/public",
diff --git a/chrome/utility/mash_service_factory.cc b/chrome/utility/mash_service_factory.cc
index da86094..0fabe9e 100644
--- a/chrome/utility/mash_service_factory.cc
+++ b/chrome/utility/mash_service_factory.cc
@@ -7,8 +7,6 @@
 #include <memory>
 
 #include "ash/ash_service.h"
-#include "ash/components/quick_launch/public/mojom/constants.mojom.h"
-#include "ash/components/quick_launch/quick_launch_application.h"
 #include "ash/components/shortcut_viewer/public/mojom/shortcut_viewer.mojom.h"
 #include "ash/components/shortcut_viewer/shortcut_viewer_application.h"
 #include "ash/components/tap_visualizer/public/mojom/tap_visualizer.mojom.h"
@@ -25,8 +23,8 @@
 // numeric values should never be reused.
 enum class MashService {
   kAsh = 0,
-  kAutoclickDeprecated = 1,  // Deleted Aug 2018, https://crbug.com/876115
-  kQuickLaunch = 2,
+  kAutoclickDeprecated = 1,    // Deleted Aug 2018, https://crbug.com/876115
+  kQuickLaunchDeprecated = 2,  // Deleted Feb 2019.
   kShortcutViewer = 3,
   kTapVisualizer = 4,
   kFontDeprecated = 5,  // Font Service is not in use for mash, but run
@@ -47,14 +45,6 @@
   return std::make_unique<ash::AshService>(std::move(request));
 }
 
-std::unique_ptr<service_manager::Service> CreateQuickLaunchService(
-    service_manager::mojom::ServiceRequest request) {
-  RecordMashServiceLaunch(MashService::kQuickLaunch);
-  logging::SetLogPrefix("quick");
-  return std::make_unique<quick_launch::QuickLaunchApplication>(
-      std::move(request));
-}
-
 std::unique_ptr<service_manager::Service> CreateShortcutViewerApp(
     service_manager::mojom::ServiceRequest request) {
   RecordMashServiceLaunch(MashService::kShortcutViewer);
@@ -82,8 +72,6 @@
     service_manager::mojom::ServiceRequest request) {
   if (service_name == ash::mojom::kServiceName)
     return CreateAshService(std::move(request));
-  if (service_name == quick_launch::mojom::kServiceName)
-    return CreateQuickLaunchService(std::move(request));
   if (service_name == shortcut_viewer::mojom::kServiceName) {
     keyboard_shortcut_viewer::ShortcutViewerApplication ::
         RegisterForTraceEvents();
diff --git a/chromecast/media/audio/cast_audio_manager.cc b/chromecast/media/audio/cast_audio_manager.cc
index 3cd6f88..ab81daf 100644
--- a/chromecast/media/audio/cast_audio_manager.cc
+++ b/chromecast/media/audio/cast_audio_manager.cc
@@ -17,6 +17,8 @@
 #include "chromecast/media/cma/backend/cma_backend_factory.h"
 #include "chromecast/public/cast_media_shlib.h"
 #include "chromecast/public/media/media_pipeline_backend.h"
+#include "media/audio/audio_device_description.h"
+
 #if defined(OS_ANDROID)
 #include "media/audio/android/audio_track_output_stream.h"
 #endif  // defined(OS_ANDROID)
@@ -174,10 +176,12 @@
     const ::media::AudioManager::LogCallback& log_callback) {
   DCHECK_EQ(::media::AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
 
-  // If |mixer_| exists, return a mixing stream. If used, the mixing stream
-  // will use the default device_id, not |device_id_or_group_id|
-  if (mixer_) {
-    DCHECK(device_id_or_group_id.empty());
+  // For non-default device IDs, we always want to use CastAudioOutputStream
+  // to allow features like redirection. For default device ID, if |mixer_|
+  // exists, return a mixing stream. If used, the mixing stream will always use
+  // the default device_id.
+  if (::media::AudioDeviceDescription::IsDefaultDevice(device_id_or_group_id) &&
+      mixer_) {
     return mixer_->MakeStream(params);
   } else {
     return new CastAudioOutputStream(
diff --git a/chromecast/media/audio/cast_audio_output_stream.cc b/chromecast/media/audio/cast_audio_output_stream.cc
index b2542a1..eac009c 100644
--- a/chromecast/media/audio/cast_audio_output_stream.cc
+++ b/chromecast/media/audio/cast_audio_output_stream.cc
@@ -82,9 +82,15 @@
   }
 }
 
-bool IsValidDeviceId(const std::string& device_id) {
-  return ::media::AudioDeviceDescription::IsCommunicationsDevice(device_id) ||
-         device_id == ::media::AudioDeviceDescription::kDefaultDeviceId;
+bool IsValidDeviceId(CastAudioManager* manager, const std::string& device_id) {
+  ::media::AudioDeviceNames valid_names;
+  manager->GetAudioOutputDeviceNames(&valid_names);
+  for (const auto& v : valid_names) {
+    if (v.unique_id == device_id) {
+      return true;
+    }
+  }
+  return false;
 }
 
 }  // namespace
@@ -154,7 +160,6 @@
   DETACH_FROM_THREAD(media_thread_checker_);
   DCHECK(audio_task_runner_);
   DCHECK(cma_backend_factory_);
-  DCHECK(IsValidDeviceId(device_id));
 
   // Set the default state.
   push_in_progress_ = false;
@@ -175,7 +180,8 @@
 
   MediaPipelineDeviceParams::AudioStreamType stream_type =
       MediaPipelineDeviceParams::kAudioStreamSoundEffects;
-  if (audio_params_.effects() & ::media::AudioParameters::MULTIZONE) {
+  if (audio_params_.effects() & ::media::AudioParameters::MULTIZONE ||
+      device_id_ != ::media::AudioDeviceDescription::kDefaultDeviceId) {
     stream_type = MediaPipelineDeviceParams::kAudioStreamNormal;
   }
 
@@ -425,7 +431,6 @@
       io_thread_("CastAudioOutputStream IO") {
   DCHECK(mixer_service_connection_factory_);
   DETACH_FROM_THREAD(io_thread_checker_);
-  DCHECK(IsValidDeviceId(device_id));
 
   base::Thread::Options options;
   options.message_loop_type = base::MessageLoop::TYPE_IO;
@@ -442,8 +447,17 @@
   media::mixer_service::MixerStreamParams params;
   params.set_content_type(ConvertContentType(GetContentType(device_id_)));
   params.set_device_id(device_id_);
-  params.set_stream_type(
-      media::mixer_service::MixerStreamParams::STREAM_TYPE_SFX);
+  // We use the default device ID for sound effects (eg volume boop), so mark
+  // those as SFX streams so they are treated correctly. Other device ID
+  // streams should act like normal (non-sound-effects) streams for redirection
+  // and other features.
+  if (device_id_ == ::media::AudioDeviceDescription::kDefaultDeviceId) {
+    params.set_stream_type(
+        media::mixer_service::MixerStreamParams::STREAM_TYPE_SFX);
+  } else {
+    params.set_stream_type(
+        media::mixer_service::MixerStreamParams::STREAM_TYPE_DEFAULT);
+  }
   params.set_sample_format(
       media::mixer_service::MixerStreamParams::SAMPLE_FORMAT_FLOAT_P);
   params.set_sample_rate(audio_params_.sample_rate());
@@ -539,12 +553,12 @@
       audio_manager_(audio_manager),
       connector_(connector),
       audio_params_(audio_params),
-      device_id_(::media::AudioDeviceDescription::IsCommunicationsDevice(
-                     device_id_or_group_id)
-                     ? ::media::AudioDeviceDescription::kCommunicationsDeviceId
+      device_id_(IsValidDeviceId(audio_manager, device_id_or_group_id)
+                     ? device_id_or_group_id
                      : ::media::AudioDeviceDescription::kDefaultDeviceId),
-      group_id_(IsValidDeviceId(device_id_or_group_id) ? ""
-                                                       : device_id_or_group_id),
+      group_id_(IsValidDeviceId(audio_manager, device_id_or_group_id)
+                    ? ""
+                    : device_id_or_group_id),
       mixer_service_connection_factory_(mixer_service_connection_factory),
       audio_weak_factory_(this) {
   DCHECK(audio_manager_);
diff --git a/components/autofill/core/browser/local_card_migration_manager.cc b/components/autofill/core/browser/local_card_migration_manager.cc
index bc9634e..174783c 100644
--- a/components/autofill/core/browser/local_card_migration_manager.cc
+++ b/components/autofill/core/browser/local_card_migration_manager.cc
@@ -222,26 +222,32 @@
     std::vector<CreditCard> migrated_cards;
     // Traverse the migratable credit cards to update each migrated card status.
     for (MigratableCreditCard& card : migratable_credit_cards_) {
+      // If it is run in a test, count all cards as successfully migrated.
+      if (observer_for_testing_) {
+        migrated_cards.push_back(card.credit_card());
+        continue;
+      }
+
       // Not every card exists in the |save_result| since some cards are
       // unchecked by the user and not migrated.
       auto it = save_result->find(card.credit_card().guid());
-      // If current card exists in the |save_result|, update its migration
-      // status.
-      if (it != save_result->end()) {
-        // Server-side response can return SUCCESS, TEMPORARY_FAILURE, or
-        // PERMANENT_FAILURE (see SaveResult enum). Branch here depending on
-        // which is received.
-        if (it->second == kMigrationResultPermanentFailure ||
-            it->second == kMigrationResultTemporaryFailure) {
-          card.set_migration_status(autofill::MigratableCreditCard::
-                                        MigrationStatus::FAILURE_ON_UPLOAD);
-        } else if (it->second == kMigrationResultSuccess) {
-          card.set_migration_status(autofill::MigratableCreditCard::
-                                        MigrationStatus::SUCCESS_ON_UPLOAD);
-          migrated_cards.push_back(card.credit_card());
-        } else {
-          NOTREACHED();
-        }
+      // If current card does not exist in the |save_result|, skip it.
+      if (it == save_result->end())
+        continue;
+
+      // Otherwise update its migration status. Server-side response can return
+      // SUCCESS, TEMPORARY_FAILURE, or PERMANENT_FAILURE (see SaveResult
+      // enum). Branch here depending on which is received.
+      if (it->second == kMigrationResultPermanentFailure ||
+          it->second == kMigrationResultTemporaryFailure) {
+        card.set_migration_status(
+            autofill::MigratableCreditCard::MigrationStatus::FAILURE_ON_UPLOAD);
+      } else if (it->second == kMigrationResultSuccess) {
+        card.set_migration_status(
+            autofill::MigratableCreditCard::MigrationStatus::SUCCESS_ON_UPLOAD);
+        migrated_cards.push_back(card.credit_card());
+      } else {
+        NOTREACHED();
       }
     }
     // Remove cards that were successfully migrated from local storage.
diff --git a/components/browser_sync/sync_auth_manager.cc b/components/browser_sync/sync_auth_manager.cc
index 7571820..c15c7a74 100644
--- a/components/browser_sync/sync_auth_manager.cc
+++ b/components/browser_sync/sync_auth_manager.cc
@@ -258,7 +258,7 @@
 }
 
 void SyncAuthManager::OnRefreshTokenUpdatedForAccount(
-    const AccountInfo& account_info) {
+    const CoreAccountInfo& account_info) {
   if (UpdateSyncAccountIfNecessary()) {
     // If the syncing account was updated as a result of this, then all that's
     // necessary has been handled; nothing else to be done here.
diff --git a/components/browser_sync/sync_auth_manager.h b/components/browser_sync/sync_auth_manager.h
index bb8b892..7b39250 100644
--- a/components/browser_sync/sync_auth_manager.h
+++ b/components/browser_sync/sync_auth_manager.h
@@ -94,7 +94,7 @@
   void OnPrimaryAccountCleared(
       const CoreAccountInfo& previous_primary_account_info) override;
   void OnRefreshTokenUpdatedForAccount(
-      const AccountInfo& account_info) override;
+      const CoreAccountInfo& account_info) override;
   void OnRefreshTokenRemovedForAccount(const std::string& account_id) override;
   void OnAccountsInCookieUpdated(
       const identity::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
diff --git a/components/component_updater/configurator_impl_unittest.cc b/components/component_updater/configurator_impl_unittest.cc
index fe77329..e5b9f8d 100644
--- a/components/component_updater/configurator_impl_unittest.cc
+++ b/components/component_updater/configurator_impl_unittest.cc
@@ -105,7 +105,7 @@
     bool PingsEnabled() const override { return false; }
     bool TestRequest() const override { return false; }
     GURL UrlSourceOverride() const override { return GURL(); }
-    int InitialDelay() const override { return initial_delay_; };
+    int InitialDelay() const override { return initial_delay_; }
 
     void set_fast_update(bool fast_update) { fast_update_ = fast_update; }
     void set_initial_delay(int initial_delay) {
@@ -152,7 +152,7 @@
     bool PingsEnabled() const override { return false; }
     bool TestRequest() const override { return test_request_; }
     GURL UrlSourceOverride() const override { return GURL(); }
-    int InitialDelay() const override { return 0; };
+    int InitialDelay() const override { return 0; }
 
     void set_test_request(bool test_request) { test_request_ = test_request; }
 
diff --git a/components/data_reduction_proxy/DEPS b/components/data_reduction_proxy/DEPS
index 136e190..3f3525b 100644
--- a/components/data_reduction_proxy/DEPS
+++ b/components/data_reduction_proxy/DEPS
@@ -2,7 +2,6 @@
   "+components/data_use_measurement/core",
   "+components/pref_registry",
   "+components/prefs",
-  "+components/previews",
   "+components/variations",
   "+crypto",
   "+google_apis",
@@ -10,6 +9,8 @@
   "+net",
   "+services/network/public/cpp",
 
+  "-components"
+
   # Data Reduction Proxy is a layered component; subdirectories must explicitly
   # introduce the ability to use the content layer as appropriate.
   "-components/data_reduction_proxy/content",
diff --git a/components/data_reduction_proxy/content/DEPS b/components/data_reduction_proxy/content/DEPS
index 774faa6..a72f56a 100644
--- a/components/data_reduction_proxy/content/DEPS
+++ b/components/data_reduction_proxy/content/DEPS
@@ -2,6 +2,7 @@
   "+components/data_reduction_proxy/core/browser",
   "+components/data_reduction_proxy/core/common",
   "+components/keyed_service",
+  "+components/previews",
   "+components/user_prefs",
   "+content/public/browser",
   "+content/public/common",
diff --git a/components/data_reduction_proxy/content/browser/BUILD.gn b/components/data_reduction_proxy/content/browser/BUILD.gn
index 1e02ba7..8984e3d 100644
--- a/components/data_reduction_proxy/content/browser/BUILD.gn
+++ b/components/data_reduction_proxy/content/browser/BUILD.gn
@@ -10,6 +10,8 @@
     "content_lofi_ui_service.h",
     "content_resource_type_provider.cc",
     "content_resource_type_provider.h",
+    "data_reduction_proxy_page_load_timing.cc",
+    "data_reduction_proxy_page_load_timing.h",
     "data_reduction_proxy_pingback_client_impl.cc",
     "data_reduction_proxy_pingback_client_impl.h",
   ]
diff --git a/components/data_reduction_proxy/content/browser/data_reduction_proxy_page_load_timing.cc b/components/data_reduction_proxy/content/browser/data_reduction_proxy_page_load_timing.cc
new file mode 100644
index 0000000..8620c19
--- /dev/null
+++ b/components/data_reduction_proxy/content/browser/data_reduction_proxy_page_load_timing.cc
@@ -0,0 +1,69 @@
+// Copyright 2016 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 "components/data_reduction_proxy/content/browser/data_reduction_proxy_page_load_timing.h"
+
+namespace data_reduction_proxy {
+
+DataReductionProxyPageLoadTiming::DataReductionProxyPageLoadTiming(
+    const base::Time& navigation_start,
+    const base::Optional<base::TimeDelta>& response_start,
+    const base::Optional<base::TimeDelta>& load_event_start,
+    const base::Optional<base::TimeDelta>& first_image_paint,
+    const base::Optional<base::TimeDelta>& first_contentful_paint,
+    const base::Optional<base::TimeDelta>& experimental_first_meaningful_paint,
+    const base::Optional<base::TimeDelta>& first_input_delay,
+    const base::Optional<base::TimeDelta>&
+        parse_blocked_on_script_load_duration,
+    const base::Optional<base::TimeDelta>& parse_stop,
+    const base::Optional<base::TimeDelta>& page_end_time,
+    const base::Optional<base::TimeDelta>& lite_page_redirect_penalty,
+    const base::Optional<previews::ServerLitePageStatus>&
+        lite_page_redirect_status,
+    const base::Optional<base::TimeDelta>&
+        navigation_start_to_main_frame_fetch_start,
+    int64_t network_bytes,
+    int64_t original_network_bytes,
+    int64_t total_page_size_bytes,
+    float cached_fraction,
+    bool app_background_occurred,
+    bool opt_out_occurred,
+    int64_t renderer_memory_usage_kb,
+    int host_id,
+    PageloadMetrics_PageEndReason page_end_reason,
+    uint32_t touch_count,
+    uint32_t scroll_count,
+    uint32_t redirect_count)
+    : navigation_start(navigation_start),
+      response_start(response_start),
+      load_event_start(load_event_start),
+      first_image_paint(first_image_paint),
+      first_contentful_paint(first_contentful_paint),
+      experimental_first_meaningful_paint(experimental_first_meaningful_paint),
+      first_input_delay(first_input_delay),
+      parse_blocked_on_script_load_duration(
+          parse_blocked_on_script_load_duration),
+      parse_stop(parse_stop),
+      page_end_time(page_end_time),
+      lite_page_redirect_penalty(lite_page_redirect_penalty),
+      lite_page_redirect_status(lite_page_redirect_status),
+      navigation_start_to_main_frame_fetch_start(
+          navigation_start_to_main_frame_fetch_start),
+      network_bytes(network_bytes),
+      original_network_bytes(original_network_bytes),
+      total_page_size_bytes(total_page_size_bytes),
+      cached_fraction(cached_fraction),
+      app_background_occurred(app_background_occurred),
+      opt_out_occurred(opt_out_occurred),
+      renderer_memory_usage_kb(renderer_memory_usage_kb),
+      host_id(host_id),
+      page_end_reason(page_end_reason),
+      touch_count(touch_count),
+      scroll_count(scroll_count),
+      redirect_count(redirect_count) {}
+
+DataReductionProxyPageLoadTiming::DataReductionProxyPageLoadTiming(
+    const DataReductionProxyPageLoadTiming& other) = default;
+
+}  // namespace data_reduction_proxy
diff --git a/components/data_reduction_proxy/content/browser/data_reduction_proxy_page_load_timing.h b/components/data_reduction_proxy/content/browser/data_reduction_proxy_page_load_timing.h
new file mode 100644
index 0000000..e02fc68
--- /dev/null
+++ b/components/data_reduction_proxy/content/browser/data_reduction_proxy_page_load_timing.h
@@ -0,0 +1,117 @@
+// Copyright 2016 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 COMPONENTS_DATA_REDUCTION_PROXY_CONTENT_BROWSER_DATA_REDUCTION_PROXY_PAGE_LOAD_TIMING_H_
+#define COMPONENTS_DATA_REDUCTION_PROXY_CONTENT_BROWSER_DATA_REDUCTION_PROXY_PAGE_LOAD_TIMING_H_
+
+#include <stdint.h>
+
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "components/data_reduction_proxy/proto/pageload_metrics.pb.h"
+#include "components/previews/core/previews_lite_page_redirect.h"
+
+namespace data_reduction_proxy {
+
+// The timing information that is relevant to the Pageload metrics pingback.
+struct DataReductionProxyPageLoadTiming {
+  DataReductionProxyPageLoadTiming(
+      const base::Time& navigation_start,
+      const base::Optional<base::TimeDelta>& response_start,
+      const base::Optional<base::TimeDelta>& load_event_start,
+      const base::Optional<base::TimeDelta>& first_image_paint,
+      const base::Optional<base::TimeDelta>& first_contentful_paint,
+      const base::Optional<base::TimeDelta>&
+          experimental_first_meaningful_paint,
+      const base::Optional<base::TimeDelta>& first_input_delay,
+      const base::Optional<base::TimeDelta>&
+          parse_blocked_on_script_load_duration,
+      const base::Optional<base::TimeDelta>& parse_stop,
+      const base::Optional<base::TimeDelta>& page_end_time,
+      const base::Optional<base::TimeDelta>& lite_page_redirect_penalty,
+      const base::Optional<previews::ServerLitePageStatus>&
+          lite_page_redirect_status,
+      const base::Optional<base::TimeDelta>&
+          navigation_start_to_main_frame_fetch_start,
+      int64_t network_bytes,
+      int64_t original_network_bytes,
+      int64_t total_page_size_bytes,
+      float cached_fraction,
+      bool app_background_occurred,
+      bool opt_out_occurred,
+      int64_t renderer_memory_usage_kb,
+      int host_id,
+      PageloadMetrics_PageEndReason page_end_reason,
+      uint32_t touch_count,
+      uint32_t scroll_count,
+      uint32_t redirect_count);
+
+  DataReductionProxyPageLoadTiming(
+      const DataReductionProxyPageLoadTiming& other);
+
+  // Time that the navigation for the associated page was initiated.
+  const base::Time navigation_start;
+
+  // All TimeDeltas are relative to navigation_start.
+
+  // Time that the first byte of the response is received.
+  const base::Optional<base::TimeDelta> response_start;
+  // Time immediately before the load event is fired.
+  const base::Optional<base::TimeDelta> load_event_start;
+  // Time when the first image is painted.
+  const base::Optional<base::TimeDelta> first_image_paint;
+  // Time when the first contentful thing (image, text, etc.) is painted.
+  const base::Optional<base::TimeDelta> first_contentful_paint;
+  // (Experimental) Time when the page's primary content is painted.
+  const base::Optional<base::TimeDelta> experimental_first_meaningful_paint;
+  // The queuing delay for the first user input on the page.
+  const base::Optional<base::TimeDelta> first_input_delay;
+  // Time that parsing was blocked by loading script.
+  const base::Optional<base::TimeDelta> parse_blocked_on_script_load_duration;
+  // Time when parsing completed.
+  const base::Optional<base::TimeDelta> parse_stop;
+  // Time when the page was ended (navigated away, Chrome backgrounded, etc).
+  const base::Optional<base::TimeDelta> page_end_time;
+  // Time spent restarting navigations while attempting a lite page redirect
+  // preview.
+  const base::Optional<base::TimeDelta> lite_page_redirect_penalty;
+  // The status of an attempted lite page redirect preview.
+  const base::Optional<previews::ServerLitePageStatus>
+      lite_page_redirect_status;
+  // The duration between the navigation start as reported by the navigation
+  // handle, and when the fetchStart of the main page HTML.
+  const base::Optional<base::TimeDelta>
+      navigation_start_to_main_frame_fetch_start;
+
+  // The number of bytes served over the network, not including headers.
+  const int64_t network_bytes;
+  // The number of bytes that would have been served over the network if the
+  // user were not using data reduction proxy, not including headers.
+  const int64_t original_network_bytes;
+  // The total number of bytes loaded for the page content, including cache.
+  const int64_t total_page_size_bytes;
+  // The fraction of bytes that were served from the cache for this page load.
+  const float cached_fraction;
+  // True when android app background occurred during the page load lifetime.
+  const bool app_background_occurred;
+  // True when the user clicks "Show Original" on the Previews infobar.
+  const bool opt_out_occurred;
+  // Kilobytes used by the renderer related to this page load. 0 if memory usage
+  // is unknown.
+  const int64_t renderer_memory_usage_kb;
+  // The host id of the renderer if there was a renderer crash.
+  const int host_id;
+  // The reason that the page load ends.
+  const PageloadMetrics_PageEndReason page_end_reason;
+  // The number of touch events that happened on the page.
+  const uint32_t touch_count;
+  // The number of scroll events that happened on the page.
+  const uint32_t scroll_count;
+  // The number of redirects that were encountered during the main frame load.
+  const uint32_t redirect_count;
+};
+
+}  // namespace data_reduction_proxy
+
+#endif  // COMPONENTS_DATA_REDUCTION_PROXY_CONTENT_BROWSER_DATA_REDUCTION_PROXY_PAGE_LOAD_TIMING_H_
diff --git a/components/data_reduction_proxy/content/browser/data_reduction_proxy_pingback_client_impl.cc b/components/data_reduction_proxy/content/browser/data_reduction_proxy_pingback_client_impl.cc
index bbbc51a..4f2271b 100644
--- a/components/data_reduction_proxy/content/browser/data_reduction_proxy_pingback_client_impl.cc
+++ b/components/data_reduction_proxy/content/browser/data_reduction_proxy_pingback_client_impl.cc
@@ -16,12 +16,13 @@
 #include "base/task/post_task.h"
 #include "base/task/task_traits.h"
 #include "base/time/time.h"
+#include "components/data_reduction_proxy/content/browser/data_reduction_proxy_page_load_timing.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_util.h"
-#include "components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
 #include "components/data_reduction_proxy/proto/client_config.pb.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
+#include "components/previews/core/previews_lite_page_redirect.h"
 #include "components/variations/net/variations_http_headers.h"
 #include "content/public/common/child_process_host.h"
 #include "net/base/load_flags.h"
@@ -36,6 +37,27 @@
 
 namespace {
 
+// Returns the HTTPSLitePagePreviewInfo_Status for a
+// |previews::ServerLitePageStatus|.
+HTTPSLitePagePreviewInfo_Status
+ProtoLitePageRedirectStatusFromLitePageRedirectStatus(
+    previews::ServerLitePageStatus status) {
+  switch (status) {
+    case previews::ServerLitePageStatus::kUnknown:
+      return HTTPSLitePagePreviewInfo_Status_UNKNOWN;
+    case previews::ServerLitePageStatus::kSuccess:
+      return HTTPSLitePagePreviewInfo_Status_SUCCESS;
+    case previews::ServerLitePageStatus::kBypass:
+      return HTTPSLitePagePreviewInfo_Status_BYPASS;
+    case previews::ServerLitePageStatus::kRedirect:
+      return HTTPSLitePagePreviewInfo_Status_REDIRECT;
+    case previews::ServerLitePageStatus::kFailure:
+      return HTTPSLitePagePreviewInfo_Status_FAILURE;
+    case previews::ServerLitePageStatus::kControl:
+      return HTTPSLitePagePreviewInfo_Status_CONTROL;
+  }
+}
+
 static const char kHistogramSucceeded[] =
     "DataReductionProxy.Pingback.Succeeded";
 static const char kHistogramAttempted[] =
@@ -146,9 +168,8 @@
         protobuf_parser::CreateDurationFromTimeDelta(
             timing.lite_page_redirect_penalty.value())
             .release());
-    info->set_status(
-        protobuf_parser::ProtoLitePageRedirectStatusFromLitePageRedirectStatus(
-            timing.lite_page_redirect_status.value()));
+    info->set_status(ProtoLitePageRedirectStatusFromLitePageRedirectStatus(
+        timing.lite_page_redirect_status.value()));
   }
 
   request->set_effective_connection_type(
diff --git a/components/data_reduction_proxy/content/browser/data_reduction_proxy_pingback_client_impl_unittest.cc b/components/data_reduction_proxy/content/browser/data_reduction_proxy_pingback_client_impl_unittest.cc
index 19b5ec0..b388196 100644
--- a/components/data_reduction_proxy/content/browser/data_reduction_proxy_pingback_client_impl_unittest.cc
+++ b/components/data_reduction_proxy/content/browser/data_reduction_proxy_pingback_client_impl_unittest.cc
@@ -21,9 +21,9 @@
 #include "base/test/scoped_task_environment.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "components/data_reduction_proxy/content/browser/data_reduction_proxy_page_load_timing.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_util.h"
-#include "components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h"
 #include "components/data_reduction_proxy/proto/client_config.pb.h"
diff --git a/components/data_reduction_proxy/core/browser/BUILD.gn b/components/data_reduction_proxy/core/browser/BUILD.gn
index 767626b..a50825d 100644
--- a/components/data_reduction_proxy/core/browser/BUILD.gn
+++ b/components/data_reduction_proxy/core/browser/BUILD.gn
@@ -71,7 +71,6 @@
       "//components/data_use_measurement/core:ascriber",
       "//components/pref_registry",
       "//components/prefs",
-      "//components/previews/core",
       "//components/variations",
       "//components/variations/net",
       "//content/public/browser",
@@ -107,7 +106,6 @@
     "//components/data_use_measurement/core:ascriber",
     "//components/pref_registry",
     "//components/prefs",
-    "//components/previews/core",
     "//components/variations",
     "//components/variations/net",
     "//content/public/browser",
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc
index 7e62475..3a54223 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc
@@ -35,7 +35,6 @@
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_type_info.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
-#include "components/previews/core/previews_decider.h"
 #include "components/variations/variations_associated_data.h"
 #include "net/base/host_port_pair.h"
 #include "net/base/load_flags.h"
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h
index 17b69c5..f336ca6 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h
@@ -25,7 +25,6 @@
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_server.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_type_info.h"
 #include "components/data_reduction_proxy/proto/client_config.pb.h"
-#include "components/previews/core/previews_experiments.h"
 #include "net/proxy_resolution/proxy_config.h"
 #include "net/proxy_resolution/proxy_retry_info.h"
 #include "services/network/public/cpp/network_connection_tracker.h"
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc
index ef9eb7e..20418b5 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc
@@ -44,8 +44,6 @@
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_type_info.h"
 #include "components/data_reduction_proxy/proto/client_config.pb.h"
-#include "components/previews/core/previews_experiments.h"
-#include "components/previews/core/test_previews_decider.h"
 #include "components/variations/variations_associated_data.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc
index dc2a149..6d7c706 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc
@@ -49,9 +49,6 @@
 #include "components/data_reduction_proxy/core/common/lofi_decider.h"
 #include "components/data_reduction_proxy/proto/client_config.pb.h"
 #include "components/data_reduction_proxy/proto/data_store.pb.h"
-#include "components/previews/core/previews_experiments.h"
-#include "components/previews/core/previews_features.h"
-#include "components/previews/core/test_previews_decider.h"
 #include "net/base/host_port_pair.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
@@ -745,9 +742,6 @@
       data_reduction_proxy_info.UseNamedProxy(proxy);
     }
 
-    // Needed as a parameter, but functionality is not tested.
-    previews::TestPreviewsDecider test_previews_decider(true);
-
     {
       // Main frame loaded. Lo-Fi should be used.
       net::HttpRequestHeaders headers;
@@ -1053,11 +1047,6 @@
 TEST_F(DataReductionProxyNetworkDelegateTest, NetHistograms) {
   Init(USE_INSECURE_PROXY);
   base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures(
-      {previews::features::kPreviews,
-       features::kDataReductionProxyDecidesTransform},
-      {});
-
   base::HistogramTester histogram_tester;
 
   std::string response_headers =
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_util.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_util.cc
index 4bf28f7..6636a40 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_util.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_util.cc
@@ -348,24 +348,6 @@
   }
 }
 
-HTTPSLitePagePreviewInfo_Status
-ProtoLitePageRedirectStatusFromLitePageRedirectStatus(
-    previews::ServerLitePageStatus status) {
-  switch (status) {
-    case previews::ServerLitePageStatus::kUnknown:
-      return HTTPSLitePagePreviewInfo_Status_UNKNOWN;
-    case previews::ServerLitePageStatus::kSuccess:
-      return HTTPSLitePagePreviewInfo_Status_SUCCESS;
-    case previews::ServerLitePageStatus::kBypass:
-      return HTTPSLitePagePreviewInfo_Status_BYPASS;
-    case previews::ServerLitePageStatus::kRedirect:
-      return HTTPSLitePagePreviewInfo_Status_REDIRECT;
-    case previews::ServerLitePageStatus::kFailure:
-      return HTTPSLitePagePreviewInfo_Status_FAILURE;
-    case previews::ServerLitePageStatus::kControl:
-      return HTTPSLitePagePreviewInfo_Status_CONTROL;
-  }
-}
 
 void TimeDeltaToDuration(const base::TimeDelta& time_delta,
                          Duration* duration) {
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_util.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_util.h
index c6929bc..a360fd1 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_util.h
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_util.h
@@ -8,10 +8,8 @@
 #include <memory>
 #include <string>
 
-#include "components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h"
 #include "components/data_reduction_proxy/proto/client_config.pb.h"
 #include "components/data_reduction_proxy/proto/pageload_metrics.pb.h"
-#include "components/previews/core/previews_lite_page_redirect.h"
 #include "net/base/network_change_notifier.h"
 #include "net/base/proxy_server.h"
 #include "net/nqe/effective_connection_type.h"
@@ -140,12 +138,6 @@
 // Returns the ProxyServer_ProxyScheme for a |net::ProxyServer::Scheme|.
 ProxyServer_ProxyScheme ProxySchemeFromScheme(net::ProxyServer::Scheme scheme);
 
-// Returns the HTTPSLitePagePreviewInfo_Status for a
-// |previews::ServerLitePageStatus|.
-HTTPSLitePagePreviewInfo_Status
-ProtoLitePageRedirectStatusFromLitePageRedirectStatus(
-    previews::ServerLitePageStatus status);
-
 // Returns the |Duration| representation of |time_delta|.
 void TimeDeltaToDuration(const base::TimeDelta& time_delta, Duration* duration);
 
diff --git a/components/data_reduction_proxy/core/common/BUILD.gn b/components/data_reduction_proxy/core/common/BUILD.gn
index 4f4cc9e..8923dfb 100644
--- a/components/data_reduction_proxy/core/common/BUILD.gn
+++ b/components/data_reduction_proxy/core/common/BUILD.gn
@@ -19,8 +19,6 @@
       "data_reduction_proxy_features.h",
       "data_reduction_proxy_headers.cc",
       "data_reduction_proxy_headers.h",
-      "data_reduction_proxy_page_load_timing.cc",
-      "data_reduction_proxy_page_load_timing.h",
       "data_reduction_proxy_params.cc",
       "data_reduction_proxy_params.h",
       "data_reduction_proxy_pref_names.cc",
@@ -46,7 +44,6 @@
     deps = [
       "//base",
       "//components/data_reduction_proxy/proto:data_reduction_proxy_proto",
-      "//components/previews/core",
       "//components/variations",
       "//google_apis",
       "//services/network/public/cpp",
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.cc b/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.cc
deleted file mode 100644
index 02c1da9..0000000
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2016 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 "components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h"
-
-namespace data_reduction_proxy {
-
-DataReductionProxyPageLoadTiming::DataReductionProxyPageLoadTiming(
-    const base::Time& navigation_start,
-    const base::Optional<base::TimeDelta>& response_start,
-    const base::Optional<base::TimeDelta>& load_event_start,
-    const base::Optional<base::TimeDelta>& first_image_paint,
-    const base::Optional<base::TimeDelta>& first_contentful_paint,
-    const base::Optional<base::TimeDelta>& experimental_first_meaningful_paint,
-    const base::Optional<base::TimeDelta>& first_input_delay,
-    const base::Optional<base::TimeDelta>&
-        parse_blocked_on_script_load_duration,
-    const base::Optional<base::TimeDelta>& parse_stop,
-    const base::Optional<base::TimeDelta>& page_end_time,
-    const base::Optional<base::TimeDelta>& lite_page_redirect_penalty,
-    const base::Optional<previews::ServerLitePageStatus>&
-        lite_page_redirect_status,
-    const base::Optional<base::TimeDelta>&
-        navigation_start_to_main_frame_fetch_start,
-    int64_t network_bytes,
-    int64_t original_network_bytes,
-    int64_t total_page_size_bytes,
-    float cached_fraction,
-    bool app_background_occurred,
-    bool opt_out_occurred,
-    int64_t renderer_memory_usage_kb,
-    int host_id,
-    PageloadMetrics_PageEndReason page_end_reason,
-    uint32_t touch_count,
-    uint32_t scroll_count,
-    uint32_t redirect_count)
-    : navigation_start(navigation_start),
-      response_start(response_start),
-      load_event_start(load_event_start),
-      first_image_paint(first_image_paint),
-      first_contentful_paint(first_contentful_paint),
-      experimental_first_meaningful_paint(experimental_first_meaningful_paint),
-      first_input_delay(first_input_delay),
-      parse_blocked_on_script_load_duration(
-          parse_blocked_on_script_load_duration),
-      parse_stop(parse_stop),
-      page_end_time(page_end_time),
-      lite_page_redirect_penalty(lite_page_redirect_penalty),
-      lite_page_redirect_status(lite_page_redirect_status),
-      navigation_start_to_main_frame_fetch_start(
-          navigation_start_to_main_frame_fetch_start),
-      network_bytes(network_bytes),
-      original_network_bytes(original_network_bytes),
-      total_page_size_bytes(total_page_size_bytes),
-      cached_fraction(cached_fraction),
-      app_background_occurred(app_background_occurred),
-      opt_out_occurred(opt_out_occurred),
-      renderer_memory_usage_kb(renderer_memory_usage_kb),
-      host_id(host_id),
-      page_end_reason(page_end_reason),
-      touch_count(touch_count),
-      scroll_count(scroll_count),
-      redirect_count(redirect_count) {}
-
-DataReductionProxyPageLoadTiming::DataReductionProxyPageLoadTiming(
-    const DataReductionProxyPageLoadTiming& other) = default;
-
-}  // namespace data_reduction_proxy
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h b/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h
deleted file mode 100644
index 3f649ba..0000000
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2016 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 COMPONENTS_DATA_REDUCTION_PROXY_CORE_COMMON_DATA_REDUCTION_PROXY_PAGE_LOAD_TIMING_H
-#define COMPONENTS_DATA_REDUCTION_PROXY_CORE_COMMON_DATA_REDUCTION_PROXY_PAGE_LOAD_TIMING_H
-
-#include <stdint.h>
-
-#include "base/optional.h"
-#include "base/time/time.h"
-#include "components/data_reduction_proxy/proto/pageload_metrics.pb.h"
-#include "components/previews/core/previews_lite_page_redirect.h"
-
-namespace data_reduction_proxy {
-
-// The timing information that is relevant to the Pageload metrics pingback.
-struct DataReductionProxyPageLoadTiming {
-  DataReductionProxyPageLoadTiming(
-      const base::Time& navigation_start,
-      const base::Optional<base::TimeDelta>& response_start,
-      const base::Optional<base::TimeDelta>& load_event_start,
-      const base::Optional<base::TimeDelta>& first_image_paint,
-      const base::Optional<base::TimeDelta>& first_contentful_paint,
-      const base::Optional<base::TimeDelta>&
-          experimental_first_meaningful_paint,
-      const base::Optional<base::TimeDelta>& first_input_delay,
-      const base::Optional<base::TimeDelta>&
-          parse_blocked_on_script_load_duration,
-      const base::Optional<base::TimeDelta>& parse_stop,
-      const base::Optional<base::TimeDelta>& page_end_time,
-      const base::Optional<base::TimeDelta>& lite_page_redirect_penalty,
-      const base::Optional<previews::ServerLitePageStatus>&
-          lite_page_redirect_status,
-      const base::Optional<base::TimeDelta>&
-          navigation_start_to_main_frame_fetch_start,
-      int64_t network_bytes,
-      int64_t original_network_bytes,
-      int64_t total_page_size_bytes,
-      float cached_fraction,
-      bool app_background_occurred,
-      bool opt_out_occurred,
-      int64_t renderer_memory_usage_kb,
-      int host_id,
-      PageloadMetrics_PageEndReason page_end_reason,
-      uint32_t touch_count,
-      uint32_t scroll_count,
-      uint32_t redirect_count);
-
-  DataReductionProxyPageLoadTiming(
-      const DataReductionProxyPageLoadTiming& other);
-
-  // Time that the navigation for the associated page was initiated.
-  const base::Time navigation_start;
-
-  // All TimeDeltas are relative to navigation_start.
-
-  // Time that the first byte of the response is received.
-  const base::Optional<base::TimeDelta> response_start;
-  // Time immediately before the load event is fired.
-  const base::Optional<base::TimeDelta> load_event_start;
-  // Time when the first image is painted.
-  const base::Optional<base::TimeDelta> first_image_paint;
-  // Time when the first contentful thing (image, text, etc.) is painted.
-  const base::Optional<base::TimeDelta> first_contentful_paint;
-  // (Experimental) Time when the page's primary content is painted.
-  const base::Optional<base::TimeDelta> experimental_first_meaningful_paint;
-  // The queuing delay for the first user input on the page.
-  const base::Optional<base::TimeDelta> first_input_delay;
-  // Time that parsing was blocked by loading script.
-  const base::Optional<base::TimeDelta> parse_blocked_on_script_load_duration;
-  // Time when parsing completed.
-  const base::Optional<base::TimeDelta> parse_stop;
-  // Time when the page was ended (navigated away, Chrome backgrounded, etc).
-  const base::Optional<base::TimeDelta> page_end_time;
-  // Time spent restarting navigations while attempting a lite page redirect
-  // preview.
-  const base::Optional<base::TimeDelta> lite_page_redirect_penalty;
-  // The status of an attempted lite page redirect preview.
-  const base::Optional<previews::ServerLitePageStatus>
-      lite_page_redirect_status;
-  // The duration between the navigation start as reported by the navigation
-  // handle, and when the fetchStart of the main page HTML.
-  const base::Optional<base::TimeDelta>
-      navigation_start_to_main_frame_fetch_start;
-
-  // The number of bytes served over the network, not including headers.
-  const int64_t network_bytes;
-  // The number of bytes that would have been served over the network if the
-  // user were not using data reduction proxy, not including headers.
-  const int64_t original_network_bytes;
-  // The total number of bytes loaded for the page content, including cache.
-  const int64_t total_page_size_bytes;
-  // The fraction of bytes that were served from the cache for this page load.
-  const float cached_fraction;
-  // True when android app background occurred during the page load lifetime.
-  const bool app_background_occurred;
-  // True when the user clicks "Show Original" on the Previews infobar.
-  const bool opt_out_occurred;
-  // Kilobytes used by the renderer related to this page load. 0 if memory usage
-  // is unknown.
-  const int64_t renderer_memory_usage_kb;
-  // The host id of the renderer if there was a renderer crash.
-  const int host_id;
-  // The reason that the page load ends.
-  const PageloadMetrics_PageEndReason page_end_reason;
-  // The number of touch events that happened on the page.
-  const uint32_t touch_count;
-  // The number of scroll events that happened on the page.
-  const uint32_t scroll_count;
-  // The number of redirects that were encountered during the main frame load.
-  const uint32_t redirect_count;
-};
-
-}  // namespace data_reduction_proxy
-
-#endif  // COMPONENTS_DATA_REDUCTION_PROXY_CORE_COMMON_DATA_REDUCTION_PROXY_PAGE_LOAD_TIMING_H
diff --git a/components/favicon/core/large_icon_service_impl.cc b/components/favicon/core/large_icon_service_impl.cc
index 1b88e824..54244ef 100644
--- a/components/favicon/core/large_icon_service_impl.cc
+++ b/components/favicon/core/large_icon_service_impl.cc
@@ -39,6 +39,8 @@
 
 using favicon_base::GoogleFaviconServerRequestStatus;
 
+const char kImageFetcherUmaClient[] = "LargeIconService";
+
 // This feature is only used for accessing field trial parameters, not for
 // switching on/off the code.
 const base::Feature kLargeIconServiceFetchingFeature{
@@ -608,7 +610,8 @@
     return;
   }
 
-  image_fetcher::ImageFetcherParams params(traffic_annotation);
+  image_fetcher::ImageFetcherParams params(traffic_annotation,
+                                           kImageFetcherUmaClient);
   image_fetcher_->FetchImage(
       server_request_url,
       base::BindOnce(&OnFetchIconFromGoogleServerComplete, favicon_service_,
diff --git a/components/feed/core/feed_image_manager.cc b/components/feed/core/feed_image_manager.cc
index 226197c..33e7a6a 100644
--- a/components/feed/core/feed_image_manager.cc
+++ b/components/feed/core/feed_image_manager.cc
@@ -27,6 +27,9 @@
 const int kDefaultGarbageCollectionExpiredDays = 30;
 const int kLongGarbageCollectionInterval = 12 * 60 * 60;  // 12 hours
 const int kShortGarbageCollectionInterval = 5 * 60;       // 5 minutes
+
+const char kImageFetcherUmaClient[] = "FeedImageManager";
+
 constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
     net::DefineNetworkTrafficAnnotation("feed_image_fetcher", R"(
         semantics {
@@ -186,12 +189,14 @@
     return;
   }
 
+  image_fetcher::ImageFetcherParams params(kTrafficAnnotation,
+                                           kImageFetcherUmaClient);
   image_fetcher_->FetchImageData(
       url,
       base::BindOnce(&FeedImageManager::OnImageFetchedFromNetwork,
                      weak_ptr_factory_.GetWeakPtr(), url_index, std::move(urls),
                      width_px, height_px, std::move(callback)),
-      kTrafficAnnotation);
+      params);
 }
 
 void FeedImageManager::OnImageFetchedFromNetwork(
diff --git a/components/gcm_driver/account_tracker.cc b/components/gcm_driver/account_tracker.cc
index c473691..1f4a7edc 100644
--- a/components/gcm_driver/account_tracker.cc
+++ b/components/gcm_driver/account_tracker.cc
@@ -67,7 +67,7 @@
 }
 
 void AccountTracker::OnRefreshTokenUpdatedForAccount(
-    const AccountInfo& account_info) {
+    const CoreAccountInfo& account_info) {
   TRACE_EVENT1("identity", "AccountTracker::OnRefreshTokenUpdatedForAccount",
                "account_id", account_info.account_id);
 
diff --git a/components/gcm_driver/account_tracker.h b/components/gcm_driver/account_tracker.h
index 04c4e6e..d72926a 100644
--- a/components/gcm_driver/account_tracker.h
+++ b/components/gcm_driver/account_tracker.h
@@ -82,7 +82,7 @@
   void OnPrimaryAccountCleared(
       const CoreAccountInfo& previous_primary_account_info) override;
   void OnRefreshTokenUpdatedForAccount(
-      const AccountInfo& account_info) override;
+      const CoreAccountInfo& account_info) override;
   void OnRefreshTokenRemovedForAccount(const std::string& account_id) override;
 
   void OnUserInfoFetchSuccess(AccountIdFetcher* fetcher,
diff --git a/components/image_fetcher/core/cache/BUILD.gn b/components/image_fetcher/core/cache/BUILD.gn
index acbb6e3..cb231c6 100644
--- a/components/image_fetcher/core/cache/BUILD.gn
+++ b/components/image_fetcher/core/cache/BUILD.gn
@@ -31,6 +31,7 @@
 source_set("cache_unit_tests") {
   testonly = true
   sources = [
+    "cached_image_fetcher_metrics_reporter_unittest.cc",
     "image_cache_unittest.cc",
     "image_data_store_disk_unittest.cc",
     "image_metadata_store_leveldb_unittest.cc",
diff --git a/components/image_fetcher/core/cache/cached_image_fetcher_metrics_reporter.cc b/components/image_fetcher/core/cache/cached_image_fetcher_metrics_reporter.cc
index 3524c62..04ac212 100644
--- a/components/image_fetcher/core/cache/cached_image_fetcher_metrics_reporter.cc
+++ b/components/image_fetcher/core/cache/cached_image_fetcher_metrics_reporter.cc
@@ -4,37 +4,97 @@
 
 #include "components/image_fetcher/core/cache/cached_image_fetcher_metrics_reporter.h"
 
+#include "base/metrics/histogram.h"
 #include "base/metrics/histogram_macros.h"
 
 namespace image_fetcher {
 
+const char CachedImageFetcherMetricsReporter::
+    kCachedImageFetcherInternalUmaClientName[] = "Internal";
+
+namespace {
+
+// 10 seconds in milliseconds.
+const int kMaxReportTimeMs = 10 * 1000;
+
+constexpr char kEventsHistogram[] = "CachedImageFetcher.Events";
+constexpr char kImageLoadFromCacheHistogram[] =
+    "CachedImageFetcher.ImageLoadFromCacheTime";
+constexpr char kImageLoadFromCacheJavaHistogram[] =
+    "CachedImageFetcher.ImageLoadFromCacheTimeJava";
+constexpr char kImageLoadFromNetworkHistogram[] =
+    "CachedImageFetcher.ImageLoadFromNetworkTime";
+constexpr char kImageLoadFromNetworkAfterCacheHitHistogram[] =
+    "CachedImageFetcher.ImageLoadFromNetworkAfterCacheHit";
+
+// Returns a raw pointer to a histogram which is owned
+base::HistogramBase* GetTimeHistogram(const std::string& histogram_name,
+                                      const std::string client_name) {
+  return base::LinearHistogram::FactoryTimeGet(
+      histogram_name + std::string(".") + client_name, base::TimeDelta(),
+      base::TimeDelta::FromMilliseconds(kMaxReportTimeMs),
+      /* one bucket every 20ms. */ kMaxReportTimeMs / 20,
+      base::Histogram::kUmaTargetedHistogramFlag);
+}
+
+}  // namespace
+
 // static
 void CachedImageFetcherMetricsReporter::ReportEvent(
+    const std::string& client_name,
     CachedImageFetcherEvent event) {
-  UMA_HISTOGRAM_ENUMERATION("CachedImageFetcher.Events", event);
+  DCHECK(!client_name.empty());
+  UMA_HISTOGRAM_ENUMERATION(kEventsHistogram, event);
+  base::LinearHistogram::FactoryGet(
+      kEventsHistogram + std::string(".") + client_name, 0,
+      static_cast<int>(CachedImageFetcherEvent::kMaxValue),
+      static_cast<int>(CachedImageFetcherEvent::kMaxValue),
+      base::Histogram::kUmaTargetedHistogramFlag)
+      ->Add(static_cast<int>(event));
 }
 
 // static
 void CachedImageFetcherMetricsReporter::ReportImageLoadFromCacheTime(
+    const std::string& client_name,
     base::Time start_time) {
+  DCHECK(!client_name.empty());
   base::TimeDelta time_delta = base::Time::Now() - start_time;
-  UMA_HISTOGRAM_TIMES("CachedImageFetcher.ImageLoadFromCacheTime", time_delta);
+  UMA_HISTOGRAM_TIMES(kImageLoadFromCacheHistogram, time_delta);
+  GetTimeHistogram(kImageLoadFromCacheHistogram, client_name)
+      ->Add(time_delta.InMilliseconds());
+}
+
+// static
+void CachedImageFetcherMetricsReporter::ReportImageLoadFromCacheTimeJava(
+    const std::string& client_name,
+    base::Time start_time) {
+  DCHECK(!client_name.empty());
+  base::TimeDelta time_delta = base::Time::Now() - start_time;
+  UMA_HISTOGRAM_TIMES(kImageLoadFromCacheJavaHistogram, time_delta);
+  GetTimeHistogram(kImageLoadFromCacheJavaHistogram, client_name)
+      ->Add(time_delta.InMilliseconds());
 }
 
 // static
 void CachedImageFetcherMetricsReporter::ReportImageLoadFromNetworkTime(
+    const std::string& client_name,
     base::Time start_time) {
+  DCHECK(!client_name.empty());
   base::TimeDelta time_delta = base::Time::Now() - start_time;
-  UMA_HISTOGRAM_TIMES("CachedImageFetcher.ImageLoadFromNetworkTime",
-                      time_delta);
+  UMA_HISTOGRAM_TIMES(kImageLoadFromNetworkHistogram, time_delta);
+  GetTimeHistogram(kImageLoadFromNetworkHistogram, client_name)
+      ->Add(time_delta.InMilliseconds());
 }
 
 // static
 void CachedImageFetcherMetricsReporter::ReportImageLoadFromNetworkAfterCacheHit(
+    const std::string& client_name,
     base::Time start_time) {
+  DCHECK(!client_name.empty());
   base::TimeDelta time_delta = base::Time::Now() - start_time;
-  UMA_HISTOGRAM_TIMES("CachedImageFetcher.ImageLoadFromNetworkAfterCacheHit",
-                      time_delta);
+  UMA_HISTOGRAM_TIMES(kImageLoadFromNetworkAfterCacheHitHistogram, time_delta);
+  GetTimeHistogram(kImageLoadFromNetworkAfterCacheHitHistogram, client_name)
+      ->Add(time_delta.InMilliseconds());
 }
 
 // static
diff --git a/components/image_fetcher/core/cache/cached_image_fetcher_metrics_reporter.h b/components/image_fetcher/core/cache/cached_image_fetcher_metrics_reporter.h
index 0717645..05dbc2b 100644
--- a/components/image_fetcher/core/cache/cached_image_fetcher_metrics_reporter.h
+++ b/components/image_fetcher/core/cache/cached_image_fetcher_metrics_reporter.h
@@ -5,6 +5,8 @@
 #ifndef COMPONENTS_IMAGE_FETCHER_CORE_CACHE_CACHED_IMAGE_FETCHER_METRICS_REPORTER_H_
 #define COMPONENTS_IMAGE_FETCHER_CORE_CACHE_CACHED_IMAGE_FETCHER_METRICS_REPORTER_H_
 
+#include <string>
+
 #include "base/time/time.h"
 
 namespace image_fetcher {
@@ -30,18 +32,30 @@
 
 class CachedImageFetcherMetricsReporter {
  public:
+  // For use in metrics that aren't client-specific.
+  static const char kCachedImageFetcherInternalUmaClientName[];
+
   // Report cache events, used by CachedImageFetcher and composing classes.
-  static void ReportEvent(CachedImageFetcherEvent event);
+  static void ReportEvent(const std::string& client_name,
+                          CachedImageFetcherEvent event);
 
   // Report the time it takes to load an image from the cache in native code.
-  static void ReportImageLoadFromCacheTime(base::Time start_time);
+  static void ReportImageLoadFromCacheTime(const std::string& client_name,
+                                           base::Time start_time);
+
+  // Report the time it takes to load an image from the cache in java code.
+  static void ReportImageLoadFromCacheTimeJava(const std::string& client_name,
+                                               base::Time start_time);
 
   // Report the time it takes to load an image from the network.
-  static void ReportImageLoadFromNetworkTime(base::Time start_time);
+  static void ReportImageLoadFromNetworkTime(const std::string& client_name,
+                                             base::Time start_time);
 
   // Report the time it takes to load an image from the network after a cache
   // hit.
-  static void ReportImageLoadFromNetworkAfterCacheHit(base::Time start_time);
+  static void ReportImageLoadFromNetworkAfterCacheHit(
+      const std::string& client_name,
+      base::Time start_time);
 
   // Report the time between cache evictions.
   static void ReportTimeSinceLastCacheLRUEviction(base::Time start_time);
diff --git a/components/image_fetcher/core/cache/cached_image_fetcher_metrics_reporter_unittest.cc b/components/image_fetcher/core/cache/cached_image_fetcher_metrics_reporter_unittest.cc
new file mode 100644
index 0000000..491c7ce
--- /dev/null
+++ b/components/image_fetcher/core/cache/cached_image_fetcher_metrics_reporter_unittest.cc
@@ -0,0 +1,141 @@
+// Copyright 2018 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 "components/image_fetcher/core/cache/cached_image_fetcher_metrics_reporter.h"
+
+#include <string>
+
+#include "base/test/metrics/histogram_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace image_fetcher {
+
+namespace {
+
+const char kUmaClientName[] = "foo";
+const char kUmaClientNameOther[] = "bar";
+
+const char kCachedImageFetcherEventHistogramName[] =
+    "CachedImageFetcher.Events";
+const char kCacheLoadHistogramName[] =
+    "CachedImageFetcher.ImageLoadFromCacheTime";
+const char kCacheLoadHistogramNameJava[] =
+    "CachedImageFetcher.ImageLoadFromCacheTimeJava";
+const char kNetworkLoadHistogramName[] =
+    "CachedImageFetcher.ImageLoadFromNetworkTime";
+const char kNetworkLoadAfterCacheHitHistogram[] =
+    "CachedImageFetcher.ImageLoadFromNetworkAfterCacheHit";
+const char kTimeSinceLastCacheLRUEviction[] =
+    "CachedImageFetcher.TimeSinceLastCacheLRUEviction";
+
+}  // namespace
+
+class CachedImageFetcherMetricsReporterTest : public testing::Test {
+ public:
+  CachedImageFetcherMetricsReporterTest() {}
+  ~CachedImageFetcherMetricsReporterTest() override = default;
+
+  base::HistogramTester& histogram_tester() { return histogram_tester_; }
+
+ private:
+  base::HistogramTester histogram_tester_;
+
+  DISALLOW_COPY_AND_ASSIGN(CachedImageFetcherMetricsReporterTest);
+};
+
+TEST_F(CachedImageFetcherMetricsReporterTest, TestReportEvent) {
+  CachedImageFetcherMetricsReporter::ReportEvent(
+      kUmaClientName, CachedImageFetcherEvent::kCacheHit);
+  CachedImageFetcherMetricsReporter::ReportEvent(
+      kUmaClientNameOther, CachedImageFetcherEvent::kCacheHit);
+  histogram_tester().ExpectBucketCount(kCachedImageFetcherEventHistogramName,
+                                       CachedImageFetcherEvent::kCacheHit, 2);
+  histogram_tester().ExpectBucketCount(
+      std::string(kCachedImageFetcherEventHistogramName)
+          .append(".")
+          .append(kUmaClientName),
+      CachedImageFetcherEvent::kCacheHit, 1);
+  histogram_tester().ExpectBucketCount(
+      std::string(kCachedImageFetcherEventHistogramName)
+          .append(".")
+          .append(kUmaClientNameOther),
+      CachedImageFetcherEvent::kCacheHit, 1);
+}
+
+TEST_F(CachedImageFetcherMetricsReporterTest,
+       TestReportImageLoadFromCacheTime) {
+  CachedImageFetcherMetricsReporter::ReportImageLoadFromCacheTime(
+      kUmaClientName, base::Time());
+  CachedImageFetcherMetricsReporter::ReportImageLoadFromCacheTime(
+      kUmaClientNameOther, base::Time());
+  histogram_tester().ExpectTotalCount(kCacheLoadHistogramName, 2);
+  histogram_tester().ExpectTotalCount(
+      std::string(kCacheLoadHistogramName).append(".").append(kUmaClientName),
+      1);
+  histogram_tester().ExpectTotalCount(std::string(kCacheLoadHistogramName)
+                                          .append(".")
+                                          .append(kUmaClientNameOther),
+                                      1);
+}
+
+TEST_F(CachedImageFetcherMetricsReporterTest,
+       TestReportImageLoadFromCacheTimeJava) {
+  CachedImageFetcherMetricsReporter::ReportImageLoadFromCacheTimeJava(
+      kUmaClientName, base::Time());
+  CachedImageFetcherMetricsReporter::ReportImageLoadFromCacheTimeJava(
+      kUmaClientNameOther, base::Time());
+  histogram_tester().ExpectTotalCount(kCacheLoadHistogramNameJava, 2);
+  histogram_tester().ExpectTotalCount(std::string(kCacheLoadHistogramNameJava)
+                                          .append(".")
+                                          .append(kUmaClientName),
+                                      1);
+  histogram_tester().ExpectTotalCount(std::string(kCacheLoadHistogramNameJava)
+                                          .append(".")
+                                          .append(kUmaClientNameOther),
+                                      1);
+}
+
+TEST_F(CachedImageFetcherMetricsReporterTest,
+       TestReportImageLoadFromNetworkTime) {
+  CachedImageFetcherMetricsReporter::ReportImageLoadFromNetworkTime(
+      kUmaClientName, base::Time());
+  CachedImageFetcherMetricsReporter::ReportImageLoadFromNetworkTime(
+      kUmaClientNameOther, base::Time());
+  histogram_tester().ExpectTotalCount(kNetworkLoadHistogramName, 2);
+  histogram_tester().ExpectTotalCount(
+      std::string(kNetworkLoadHistogramName).append(".").append(kUmaClientName),
+      1);
+  histogram_tester().ExpectTotalCount(std::string(kNetworkLoadHistogramName)
+                                          .append(".")
+                                          .append(kUmaClientNameOther),
+                                      1);
+}
+
+TEST_F(CachedImageFetcherMetricsReporterTest,
+       TestReportImageLoadFromNetworkAfterCacheHit) {
+  CachedImageFetcherMetricsReporter::ReportImageLoadFromNetworkAfterCacheHit(
+      kUmaClientName, base::Time());
+  CachedImageFetcherMetricsReporter::ReportImageLoadFromNetworkAfterCacheHit(
+      kUmaClientNameOther, base::Time());
+  histogram_tester().ExpectTotalCount(kNetworkLoadAfterCacheHitHistogram, 2);
+  histogram_tester().ExpectTotalCount(
+      std::string(kNetworkLoadAfterCacheHitHistogram)
+          .append(".")
+          .append(kUmaClientName),
+      1);
+  histogram_tester().ExpectTotalCount(
+      std::string(kNetworkLoadAfterCacheHitHistogram)
+          .append(".")
+          .append(kUmaClientNameOther),
+      1);
+}
+
+TEST_F(CachedImageFetcherMetricsReporterTest,
+       TestReportTimeSinceLastCacheLRUEviction) {
+  CachedImageFetcherMetricsReporter::ReportTimeSinceLastCacheLRUEviction(
+      base::Time());
+  histogram_tester().ExpectTotalCount(kTimeSinceLastCacheLRUEviction, 1);
+}
+
+}  // namespace image_fetcher
diff --git a/components/image_fetcher/core/cache/image_cache.cc b/components/image_fetcher/core/cache/image_cache.cc
index 1f61efb8..14c5bab 100644
--- a/components/image_fetcher/core/cache/image_cache.cc
+++ b/components/image_fetcher/core/cache/image_cache.cc
@@ -138,6 +138,8 @@
   // TODO(wylieb): Consider delaying eviction as new requests come in via
   // separate weak pointers.
   CachedImageFetcherMetricsReporter::ReportEvent(
+      CachedImageFetcherMetricsReporter::
+          kCachedImageFetcherInternalUmaClientName,
       CachedImageFetcherEvent::kCacheStartupEvictionStarted);
 
   // Once all the queued requests are taken care of, run eviction.
@@ -268,6 +270,8 @@
   }
 
   CachedImageFetcherMetricsReporter::ReportEvent(
+      CachedImageFetcherMetricsReporter::
+          kCachedImageFetcherInternalUmaClientName,
       CachedImageFetcherEvent::kCacheStartupEvictionFinished);
 }
 
diff --git a/components/image_fetcher/core/cached_image_fetcher.cc b/components/image_fetcher/core/cached_image_fetcher.cc
index 18a45b8..51ff84a 100644
--- a/components/image_fetcher/core/cached_image_fetcher.cc
+++ b/components/image_fetcher/core/cached_image_fetcher.cc
@@ -100,6 +100,9 @@
       /* cache_hit_before_network_request */ false,
       /* start_time */ base::Time::Now()};
 
+  CachedImageFetcherMetricsReporter::ReportEvent(
+      request.params.uma_client_name(), CachedImageFetcherEvent::kImageRequest);
+
   // First, try to load the image from the cache, then try the network.
   image_cache_->LoadImage(
       read_only_, image_url.spec(),
@@ -107,9 +110,6 @@
                      weak_ptr_factory_.GetWeakPtr(), std::move(request),
                      std::move(image_data_callback),
                      std::move(image_callback)));
-
-  CachedImageFetcherMetricsReporter::ReportEvent(
-      CachedImageFetcherEvent::kImageRequest);
 }
 
 void CachedImageFetcher::OnImageFetchedFromCache(
@@ -118,31 +118,29 @@
     ImageFetcherCallback image_callback,
     std::string image_data) {
   if (image_data.empty()) {
+    CachedImageFetcherMetricsReporter::ReportEvent(
+        request.params.uma_client_name(), CachedImageFetcherEvent::kCacheMiss);
+
     // Fetching from the DB failed, start a network fetch.
     EnqueueFetchImageFromNetwork(std::move(request),
                                  std::move(image_data_callback),
                                  std::move(image_callback));
-
-    CachedImageFetcherMetricsReporter::ReportEvent(
-        CachedImageFetcherEvent::kCacheMiss);
   } else {
     DataCallbackIfPresent(std::move(image_data_callback), image_data,
                           RequestMetadata());
+    CachedImageFetcherMetricsReporter::ReportEvent(
+        request.params.uma_client_name(), CachedImageFetcherEvent::kCacheHit);
 
     // Only continue with decoding if the user actually asked for an image.
     if (!image_callback.is_null()) {
       GetImageDecoder()->DecodeImage(
-          image_data,
-          /* The frame size had already been chosen during fetch. */
-          gfx::Size(),
+          image_data, gfx::Size(),
           base::BindRepeating(&CachedImageFetcher::OnImageDecodedFromCache,
                               weak_ptr_factory_.GetWeakPtr(),
                               std::move(request),
                               base::Passed(std::move(image_data_callback)),
                               base::Passed(std::move(image_callback))));
     }
-    CachedImageFetcherMetricsReporter::ReportEvent(
-        CachedImageFetcherEvent::kCacheHit);
   }
 }
 
@@ -159,11 +157,12 @@
                                  std::move(image_callback));
 
     CachedImageFetcherMetricsReporter::ReportEvent(
+        request.params.uma_client_name(),
         CachedImageFetcherEvent::kCacheDecodingError);
   } else {
     ImageCallbackIfPresent(std::move(image_callback), image, RequestMetadata());
     CachedImageFetcherMetricsReporter::ReportImageLoadFromCacheTime(
-        request.start_time);
+        request.params.uma_client_name(), request.start_time);
   }
 }
 
@@ -203,43 +202,46 @@
   // caller.
   ImageCallbackIfPresent(std::move(image_callback), image, request_metadata);
 
+  // Report to different histograms depending upon if there was a cache hit.
+  if (request.cache_hit_before_network_request) {
+    CachedImageFetcherMetricsReporter::ReportImageLoadFromNetworkAfterCacheHit(
+        request.params.uma_client_name(), request.start_time);
+  } else {
+    CachedImageFetcherMetricsReporter::ReportImageLoadFromNetworkTime(
+        request.params.uma_client_name(), request.start_time);
+  }
+
   // Copy the image data out and store it on disk.
   const SkBitmap* bitmap = image.IsEmpty() ? nullptr : image.ToSkBitmap();
   // If the bitmap is null or otherwise not ready, skip encoding.
   if (bitmap == nullptr || bitmap->isNull() || !bitmap->readyToDraw()) {
-    StoreEncodedData(request.url, "");
     CachedImageFetcherMetricsReporter::ReportEvent(
+        request.params.uma_client_name(),
         CachedImageFetcherEvent::kTotalFailure);
+    StoreEncodedData(std::move(request), "");
   } else {
     // Post a task to another thread to encode the image data downloaded.
     base::PostTaskAndReplyWithResult(
         FROM_HERE, base::BindOnce(&EncodeSkBitmapToPNG, *bitmap),
         base::BindOnce(&CachedImageFetcher::StoreEncodedData,
-                       weak_ptr_factory_.GetWeakPtr(), request.url));
-  }
-
-  // Report to different histograms depending upon if there was a cache hit.
-  if (request.cache_hit_before_network_request) {
-    CachedImageFetcherMetricsReporter::ReportImageLoadFromNetworkAfterCacheHit(
-        request.start_time);
-  } else {
-    CachedImageFetcherMetricsReporter::ReportImageLoadFromNetworkTime(
-        request.start_time);
+                       weak_ptr_factory_.GetWeakPtr(), std::move(request)));
   }
 }
 
-void CachedImageFetcher::StoreEncodedData(const GURL& url,
+void CachedImageFetcher::StoreEncodedData(CachedImageFetcherRequest request,
                                           std::string image_data) {
+  std::string url = request.url.spec();
   // If the image is empty, delete the image.
   if (image_data.empty()) {
     CachedImageFetcherMetricsReporter::ReportEvent(
+        request.params.uma_client_name(),
         CachedImageFetcherEvent::kTranscodingError);
-    image_cache_->DeleteImage(url.spec());
+    image_cache_->DeleteImage(std::move(url));
     return;
   }
 
   if (!read_only_) {
-    image_cache_->SaveImage(url.spec(), std::move(image_data));
+    image_cache_->SaveImage(std::move(url), std::move(image_data));
   }
 }
 
diff --git a/components/image_fetcher/core/cached_image_fetcher.h b/components/image_fetcher/core/cached_image_fetcher.h
index a33be0b..12ac2dc 100644
--- a/components/image_fetcher/core/cached_image_fetcher.h
+++ b/components/image_fetcher/core/cached_image_fetcher.h
@@ -70,7 +70,8 @@
                                  ImageFetcherCallback image_callback,
                                  const gfx::Image& image,
                                  const RequestMetadata& request_metadata);
-  void StoreEncodedData(const GURL& url, std::string image_data);
+  void StoreEncodedData(CachedImageFetcherRequest request,
+                        std::string image_data);
 
   // Whether the ImageChache is allowed to be modified in any way from requests
   // made by this CachedImageFetcher. This includes updating last used times,
diff --git a/components/image_fetcher/core/cached_image_fetcher_unittest.cc b/components/image_fetcher/core/cached_image_fetcher_unittest.cc
index e87aeba..28c910b 100644
--- a/components/image_fetcher/core/cached_image_fetcher_unittest.cc
+++ b/components/image_fetcher/core/cached_image_fetcher_unittest.cc
@@ -47,6 +47,8 @@
 namespace {
 
 const GURL kImageUrl = GURL("http://gstatic.img.com/foo.jpg");
+
+constexpr char kUmaClientName[] = "TestUma";
 constexpr char kImageData[] = "data";
 
 const char kCachedImageFetcherEventHistogramName[] =
@@ -174,7 +176,7 @@
   EXPECT_CALL(image_callback, Run(NonEmptyImage(), _));
   cached_image_fetcher()->FetchImageAndData(
       kImageUrl, data_callback.Get(), image_callback.Get(),
-      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kUmaClientName));
 
   RunUntilIdle();
 
@@ -201,7 +203,7 @@
     EXPECT_CALL(image_callback, Run(EmptyImage(), _));
     cached_image_fetcher()->FetchImageAndData(
         kImageUrl, data_callback.Get(), image_callback.Get(),
-        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kUmaClientName));
     RunUntilIdle();
 
     histogram_tester().ExpectBucketCount(kCachedImageFetcherEventHistogramName,
@@ -221,7 +223,7 @@
     EXPECT_CALL(image_callback, Run(NonEmptyImage(), _));
     cached_image_fetcher()->FetchImageAndData(
         kImageUrl, data_callback.Get(), image_callback.Get(),
-        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kUmaClientName));
     RunUntilIdle();
   }
 }
@@ -238,7 +240,7 @@
     EXPECT_CALL(image_callback, Run(NonEmptyImage(), _));
     cached_image_fetcher()->FetchImageAndData(
         kImageUrl, data_callback.Get(), image_callback.Get(),
-        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kUmaClientName));
 
     RunUntilIdle();
 
@@ -270,7 +272,7 @@
     EXPECT_CALL(image_callback, Run(NonEmptyImage(), _));
     cached_image_fetcher()->FetchImageAndData(
         kImageUrl, data_callback.Get(), image_callback.Get(),
-        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kUmaClientName));
 
     RunUntilIdle();
   }
@@ -289,7 +291,7 @@
     EXPECT_CALL(image_callback, Run(NonEmptyImage(), _));
     cached_image_fetcher()->FetchImageAndData(
         kImageUrl, data_callback.Get(), image_callback.Get(),
-        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kUmaClientName));
 
     RunUntilIdle();
 
@@ -325,7 +327,7 @@
   test_url_loader_factory()->AddResponse(kImageUrl.spec(), kImageData);
   cached_image_fetcher()->FetchImageAndData(
       kImageUrl, data_callback.Get(), image_callback.Get(),
-      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kUmaClientName));
   RunUntilIdle();
 
   histogram_tester().ExpectTotalCount(kNetworkLoadAfterCacheHitHistogram, 1);
diff --git a/components/image_fetcher/core/image_fetcher.cc b/components/image_fetcher/core/image_fetcher.cc
index ef0d2f1..e15d4f6 100644
--- a/components/image_fetcher/core/image_fetcher.cc
+++ b/components/image_fetcher/core/image_fetcher.cc
@@ -7,12 +7,16 @@
 namespace image_fetcher {
 
 ImageFetcherParams::ImageFetcherParams(
-    const net::NetworkTrafficAnnotationTag network_traffic_annotation_tag)
-    : network_traffic_annotation_tag_(network_traffic_annotation_tag) {}
+    const net::NetworkTrafficAnnotationTag network_traffic_annotation_tag,
+    std::string uma_client_name)
+    : network_traffic_annotation_tag_(network_traffic_annotation_tag),
+      uma_client_name_(uma_client_name) {}
 
 ImageFetcherParams::ImageFetcherParams(const ImageFetcherParams& params) =
     default;
 
 ImageFetcherParams::ImageFetcherParams(ImageFetcherParams&& params) = default;
 
+ImageFetcherParams::~ImageFetcherParams() = default;
+
 }  // namespace image_fetcher
\ No newline at end of file
diff --git a/components/image_fetcher/core/image_fetcher.h b/components/image_fetcher/core/image_fetcher.h
index 13eae63..e92c366 100644
--- a/components/image_fetcher/core/image_fetcher.h
+++ b/components/image_fetcher/core/image_fetcher.h
@@ -33,12 +33,15 @@
 //   the downloaded image to the given dimensions.
 class ImageFetcherParams {
  public:
+  // Sets the UMA client name to report feature-specific metrics. Make sure
+  // |uma_client_name| is also present in histograms.xml.
   ImageFetcherParams(
-      net::NetworkTrafficAnnotationTag network_traffic_annotation_tag);
+      net::NetworkTrafficAnnotationTag network_traffic_annotation_tag,
+      std::string uma_client_name);
   ImageFetcherParams(const ImageFetcherParams& params);
   ImageFetcherParams(ImageFetcherParams&& params);
 
-  ~ImageFetcherParams() = default;
+  ~ImageFetcherParams();
 
   const net::NetworkTrafficAnnotationTag traffic_annotation() const {
     return network_traffic_annotation_tag_;
@@ -58,11 +61,14 @@
 
   gfx::Size frame_size() const { return desired_frame_size_; }
 
+  const std::string& uma_client_name() const { return uma_client_name_; }
+
  private:
   const net::NetworkTrafficAnnotationTag network_traffic_annotation_tag_;
 
   base::Optional<int64_t> max_download_bytes_;
   gfx::Size desired_frame_size_;
+  std::string uma_client_name_;
 };
 
 // A class used to fetch server images. It can be called from any thread and the
diff --git a/components/image_fetcher/core/image_fetcher_impl_unittest.cc b/components/image_fetcher/core/image_fetcher_impl_unittest.cc
index fcbebf7..61ad10c 100644
--- a/components/image_fetcher/core/image_fetcher_impl_unittest.cc
+++ b/components/image_fetcher/core/image_fetcher_impl_unittest.cc
@@ -31,8 +31,10 @@
 
 namespace {
 
-const char kImageData[] = "data";
-const char kImageURL[] = "http://image.test/test.png";
+constexpr char kImageData[] = "data";
+constexpr char kImageURL[] = "http://image.test/test.png";
+
+constexpr char kImageUmaName[] = "TestUma";
 
 class ImageFetcherImplTest : public testing::Test {
  public:
@@ -79,7 +81,7 @@
 
   image_fetcher()->FetchImageAndData(
       GURL(kImageURL), data_callback.Get(), image_callback.Get(),
-      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kImageUmaName));
   RunUntilIdle();
 }
 
@@ -93,7 +95,7 @@
 
   image_fetcher()->FetchImageAndData(
       GURL(kImageURL), data_callback1.Get(), image_callback1.Get(),
-      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kImageUmaName));
 
   base::MockCallback<ImageDataFetcherCallback> data_callback2;
   base::MockCallback<ImageFetcherCallback> image_callback2;
@@ -103,7 +105,7 @@
   // This call happens before the network request completes.
   image_fetcher()->FetchImageAndData(
       GURL(kImageURL), data_callback2.Get(), image_callback2.Get(),
-      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kImageUmaName));
 
   base::MockCallback<ImageDataFetcherCallback> data_callback3;
   base::MockCallback<ImageFetcherCallback> image_callback3;
@@ -116,7 +118,7 @@
     test_url_loader_factory()->AddResponse(kImageURL, "", net::HTTP_NOT_FOUND);
     image_fetcher()->FetchImageAndData(
         GURL(kImageURL), data_callback3.Get(), image_callback3.Get(),
-        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kImageUmaName));
   }));
 
   RunUntilIdle();
@@ -133,7 +135,7 @@
 
   image_fetcher()->FetchImageAndData(
       GURL(kImageURL), data_callback1.Get(), image_callback1.Get(),
-      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kImageUmaName));
 
   base::MockCallback<ImageDataFetcherCallback> data_callback2;
   base::MockCallback<ImageFetcherCallback> image_callback2;
@@ -142,7 +144,7 @@
 
   image_fetcher()->FetchImageAndData(
       GURL(kImageURL), data_callback2.Get(), image_callback2.Get(),
-      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kImageUmaName));
 
   RunUntilIdle();
 }
@@ -155,7 +157,7 @@
 
   image_fetcher()->FetchImageAndData(
       GURL(kImageURL), data_callback.Get(), ImageFetcherCallback(),
-      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kImageUmaName));
 
   RunUntilIdle();
 }
@@ -167,14 +169,14 @@
 
   image_fetcher()->FetchImageAndData(
       GURL(kImageURL), data_callback.Get(), ImageFetcherCallback(),
-      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kImageUmaName));
 
   base::MockCallback<ImageFetcherCallback> image_callback;
   EXPECT_CALL(image_callback, Run(ValidImage(), _));
 
   image_fetcher()->FetchImageAndData(
       GURL(kImageURL), ImageDataFetcherCallback(), image_callback.Get(),
-      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kImageUmaName));
 
   RunUntilIdle();
 }
@@ -187,7 +189,7 @@
 
   image_fetcher()->FetchImageAndData(
       GURL(kImageURL), ImageDataFetcherCallback(), image_callback.Get(),
-      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+      ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kImageUmaName));
 
   base::MockCallback<ImageDataFetcherCallback> data_callback;
   EXPECT_CALL(data_callback, Run(kImageData, _));
@@ -198,7 +200,7 @@
     test_url_loader_factory()->AddResponse(kImageURL, "", net::HTTP_NOT_FOUND);
     image_fetcher()->FetchImageAndData(
         GURL(kImageURL), data_callback.Get(), ImageFetcherCallback(),
-        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS));
+        ImageFetcherParams(TRAFFIC_ANNOTATION_FOR_TESTS, kImageUmaName));
   }));
 
   RunUntilIdle();
diff --git a/components/image_fetcher/core/mock_image_fetcher.h b/components/image_fetcher/core/mock_image_fetcher.h
index 021c5b74..15b98d78 100644
--- a/components/image_fetcher/core/mock_image_fetcher.h
+++ b/components/image_fetcher/core/mock_image_fetcher.h
@@ -15,11 +15,6 @@
   MockImageFetcher();
   ~MockImageFetcher() override;
 
-  MOCK_METHOD1(SetDataUseServiceName,
-               void(data_use_measurement::DataUseUserData::ServiceName));
-  MOCK_METHOD1(SetImageDownloadLimit,
-               void(base::Optional<int64_t> max_download_bytes));
-  MOCK_METHOD1(SetDesiredImageFrameSize, void(const gfx::Size&));
   MOCK_METHOD4(FetchImageAndData_,
                void(const GURL&,
                     ImageDataFetcherCallback*,
diff --git a/components/invalidation/impl/profile_identity_provider.cc b/components/invalidation/impl/profile_identity_provider.cc
index 8b01277..b7dbdb5 100644
--- a/components/invalidation/impl/profile_identity_provider.cc
+++ b/components/invalidation/impl/profile_identity_provider.cc
@@ -111,7 +111,7 @@
 }
 
 void ProfileIdentityProvider::OnRefreshTokenUpdatedForAccount(
-    const AccountInfo& account_info) {
+    const CoreAccountInfo& account_info) {
   ProcessRefreshTokenUpdateForAccount(account_info.account_id);
 }
 
diff --git a/components/invalidation/impl/profile_identity_provider.h b/components/invalidation/impl/profile_identity_provider.h
index 7341fe31..aefbd56 100644
--- a/components/invalidation/impl/profile_identity_provider.h
+++ b/components/invalidation/impl/profile_identity_provider.h
@@ -33,7 +33,7 @@
 
   // identity::IdentityManager::Observer:
   void OnRefreshTokenUpdatedForAccount(
-      const AccountInfo& account_info) override;
+      const CoreAccountInfo& account_info) override;
   void OnRefreshTokenRemovedForAccount(const std::string& account_id) override;
 
  private:
diff --git a/components/navigation_interception/intercept_navigation_throttle.cc b/components/navigation_interception/intercept_navigation_throttle.cc
index 2b2b89e..2010540 100644
--- a/components/navigation_interception/intercept_navigation_throttle.cc
+++ b/components/navigation_interception/intercept_navigation_throttle.cc
@@ -9,14 +9,16 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/timer/elapsed_timer.h"
+#include "build/build_config.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_handle.h"
 #include "url/gurl.h"
 
 namespace navigation_interception {
 
+// Note: this feature is a no-op on non-Android platforms.
 const base::Feature InterceptNavigationThrottle::kAsyncCheck{
-    "AsyncNavigationIntercept", base::FEATURE_DISABLED_BY_DEFAULT};
+    "AsyncNavigationIntercept", base::FEATURE_ENABLED_BY_DEFAULT};
 
 InterceptNavigationThrottle::InterceptNavigationThrottle(
     content::NavigationHandle* navigation_handle,
@@ -49,21 +51,7 @@
 }
 
 content::NavigationThrottle::ThrottleCheckResult
-InterceptNavigationThrottle::WillFailRequest() {
-  return WillFinish();
-}
-
-content::NavigationThrottle::ThrottleCheckResult
 InterceptNavigationThrottle::WillProcessResponse() {
-  return WillFinish();
-}
-
-const char* InterceptNavigationThrottle::GetNameForLogging() {
-  return "InterceptNavigationThrottle";
-}
-
-content::NavigationThrottle::ThrottleCheckResult
-InterceptNavigationThrottle::WillFinish() {
   DCHECK(!deferring_);
   if (should_ignore_)
     return content::NavigationThrottle::CANCEL_AND_IGNORE;
@@ -76,6 +64,10 @@
   return content::NavigationThrottle::PROCEED;
 }
 
+const char* InterceptNavigationThrottle::GetNameForLogging() {
+  return "InterceptNavigationThrottle";
+}
+
 content::NavigationThrottle::ThrottleCheckResult
 InterceptNavigationThrottle::CheckIfShouldIgnoreNavigation(bool is_redirect) {
   if (ShouldCheckAsynchronously()) {
@@ -120,14 +112,19 @@
 
 bool InterceptNavigationThrottle::ShouldCheckAsynchronously() const {
   // Do not apply the async optimization for:
+  // - Non-Android platforms (where the check is always fast)
   // - POST navigations, to ensure we aren't violating idempotency.
   // - Subframe navigations, which aren't observed on Android, and should be
   //   fast on other platforms.
   // - non-http/s URLs, which are more likely to be intercepted.
+#if defined(OS_ANDROID)
   return navigation_handle()->IsInMainFrame() &&
          !navigation_handle()->IsPost() &&
          navigation_handle()->GetURL().SchemeIsHTTPOrHTTPS() &&
          base::FeatureList::IsEnabled(kAsyncCheck);
+#else
+  return false;
+#endif
 }
 
 NavigationParams InterceptNavigationThrottle::GetNavigationParams(
diff --git a/components/navigation_interception/intercept_navigation_throttle.h b/components/navigation_interception/intercept_navigation_throttle.h
index 0b154bc..ba12086 100644
--- a/components/navigation_interception/intercept_navigation_throttle.h
+++ b/components/navigation_interception/intercept_navigation_throttle.h
@@ -41,14 +41,10 @@
   // content::NavigationThrottle implementation:
   ThrottleCheckResult WillStartRequest() override;
   ThrottleCheckResult WillRedirectRequest() override;
-  ThrottleCheckResult WillFailRequest() override;
   ThrottleCheckResult WillProcessResponse() override;
   const char* GetNameForLogging() override;
 
  private:
-  // To be called on either WillFailRequest or WillProcessResponse.
-  ThrottleCheckResult WillFinish();
-
   ThrottleCheckResult CheckIfShouldIgnoreNavigation(bool is_redirect);
   void RunCheckAsync(const NavigationParams& params);
 
diff --git a/components/ntp_snippets/remote/cached_image_fetcher.cc b/components/ntp_snippets/remote/cached_image_fetcher.cc
index 41d84b2..181c449 100644
--- a/components/ntp_snippets/remote/cached_image_fetcher.cc
+++ b/components/ntp_snippets/remote/cached_image_fetcher.cc
@@ -15,7 +15,11 @@
 #include "ui/gfx/image/image.h"
 
 namespace ntp_snippets {
+
 namespace {
+
+constexpr char kImageFetcherUmaClientName[] = "NtpSnippets";
+
 constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
     net::DefineNetworkTrafficAnnotation("remote_suggestions_provider", R"(
         semantics {
@@ -151,7 +155,8 @@
                        base::Unretained(this), std::move(image_callback));
   }
 
-  image_fetcher::ImageFetcherParams params(kTrafficAnnotation);
+  image_fetcher::ImageFetcherParams params(kTrafficAnnotation,
+                                           kImageFetcherUmaClientName);
   image_fetcher_->FetchImageAndData(
       url,
       base::BindOnce(&CachedImageFetcher::SaveImageAndInvokeDataCallback,
diff --git a/components/ntp_tiles/icon_cacher_impl.cc b/components/ntp_tiles/icon_cacher_impl.cc
index e6efa6a..a4a6203 100644
--- a/components/ntp_tiles/icon_cacher_impl.cc
+++ b/components/ntp_tiles/icon_cacher_impl.cc
@@ -37,6 +37,8 @@
 constexpr int kDefaultTileIconMinSizePx = 1;
 constexpr int kDefaultTileIconDesiredSizePx = 96;
 
+const char kImageFetcherUmaClient[] = "IconCacher";
+
 constexpr char kTileIconMinSizePxFieldParam[] = "min_size";
 constexpr char kTileIconDesiredSizePxFieldParam[] = "desired_size";
 
@@ -133,7 +135,8 @@
           setting: "This feature cannot be disabled in settings."
           policy_exception_justification: "Not implemented."
         })");
-  image_fetcher::ImageFetcherParams params(traffic_annotation);
+  image_fetcher::ImageFetcherParams params(traffic_annotation,
+                                           kImageFetcherUmaClient);
   // For images with multiple frames, prefer one of size 128x128px.
   params.set_frame_size(gfx::Size(kDesiredFrameSize, kDesiredFrameSize));
   image_fetcher_->FetchImage(
@@ -141,7 +144,7 @@
       base::BindOnce(&IconCacherImpl::OnPopularSitesFaviconDownloaded,
                      base::Unretained(this), site,
                      std::move(preliminary_callback)),
-      params);
+      std::move(params));
 }
 
 void IconCacherImpl::OnPopularSitesFaviconDownloaded(
diff --git a/components/offline_pages/core/prefetch/thumbnail_fetch_by_url.cc b/components/offline_pages/core/prefetch/thumbnail_fetch_by_url.cc
index db24254..54566cd 100644
--- a/components/offline_pages/core/prefetch/thumbnail_fetch_by_url.cc
+++ b/components/offline_pages/core/prefetch/thumbnail_fetch_by_url.cc
@@ -13,6 +13,8 @@
 
 namespace {
 
+constexpr char kImageFetcherUmaClientName[] = "OfflinePages";
+
 constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
     net::DefineNetworkTrafficAnnotation("prefetch_thumbnail", R"(
         semantics {
@@ -53,7 +55,8 @@
         std::move(callback).Run(image_data);
       };
 
-  image_fetcher::ImageFetcherParams params(kTrafficAnnotation);
+  image_fetcher::ImageFetcherParams params(kTrafficAnnotation,
+                                           kImageFetcherUmaClientName);
 
   fetcher->FetchImageData(thumbnail_url,
                           base::BindOnce(forward_callback, std::move(callback)),
diff --git a/components/omnibox/browser/omnibox_popup_model.h b/components/omnibox/browser/omnibox_popup_model.h
index a434546..8b630cf 100644
--- a/components/omnibox/browser/omnibox_popup_model.h
+++ b/components/omnibox/browser/omnibox_popup_model.h
@@ -149,6 +149,10 @@
   // the tab key.
   bool SelectedLineHasButton();
 
+  // If |closes| is set true, the popup will close when the omnibox is blurred.
+  bool popup_closes_on_blur() const { return popup_closes_on_blur_; }
+  void set_popup_closes_on_blur(bool closes) { popup_closes_on_blur_ = closes; }
+
   // The token value for selected_line_ and functions dealing with a "line
   // number" that indicates "no line".
   static const size_t kNoMatch;
@@ -181,6 +185,10 @@
   // The user has manually selected a match.
   bool has_selected_match_;
 
+  // True if the popup should close on omnibox blur. This defaults to true, and
+  // is only false while a bubble related to the popup contents is shown.
+  bool popup_closes_on_blur_ = true;
+
   // Observers.
   base::ObserverList<OmniboxPopupModelObserver>::Unchecked observers_;
 
diff --git a/components/previews/DEPS b/components/previews/DEPS
new file mode 100644
index 0000000..ba62364
--- /dev/null
+++ b/components/previews/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+  # Previews is a layered component; subdirectories must introduce
+  # allowances of //content dependencies as appropriate.
+  "-components",
+  "-components/data_reduction_proxy/",
+  "-components/previews/content",
+]
\ No newline at end of file
diff --git a/components/previews/content/DEPS b/components/previews/content/DEPS
index 579b2e9..9c35d75 100644
--- a/components/previews/content/DEPS
+++ b/components/previews/content/DEPS
@@ -2,6 +2,7 @@
   "+components/blacklist/opt_out_blacklist",
   "+components/leveldb_proto",
   "+components/optimization_guide",
+  "+components/previews/core",
   "+components/url_matcher",
   "+components/variations",
   "+content/public/common",
diff --git a/components/send_tab_to_self/send_tab_to_self_bridge.cc b/components/send_tab_to_self/send_tab_to_self_bridge.cc
index c11495f8..8d6c627 100644
--- a/components/send_tab_to_self/send_tab_to_self_bridge.cc
+++ b/components/send_tab_to_self/send_tab_to_self_bridge.cc
@@ -54,10 +54,16 @@
 base::Optional<syncer::ModelError> SendTabToSelfBridge::ApplySyncChanges(
     std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
     syncer::EntityChangeList entity_changes) {
+  std::vector<const SendTabToSelfEntry*> added;
+  std::vector<std::string> removed;
   for (syncer::EntityChange& change : entity_changes) {
     if (change.type() == syncer::EntityChange::ACTION_DELETE) {
       entries_.erase(change.storage_key());
+      removed.push_back(change.storage_key());
     } else {
+      // syncer::EntityChange::ACTION_UPDATE is not supported by this bridge
+      DCHECK(change.type() == syncer::EntityChange::ACTION_ADD);
+
       const sync_pb::SendTabToSelfSpecifics& specifics =
           change.data().specifics.send_tab_to_self();
       // TODO(jeffreycohen): FromProto expects a valid entry. External data
@@ -65,11 +71,15 @@
       std::unique_ptr<SendTabToSelfEntry> entry =
           SendTabToSelfEntry::FromProto(specifics, clock_->Now());
       // This entry is new. Add it to the model.
+      added.push_back(entry.get());
       entries_[entry->GetGUID()] = std::move(entry);
     }
   }
-  if (!entity_changes.empty()) {
-    NotifySendTabToSelfModelChanged();
+  if (!removed.empty()) {
+    NotifySendTabToSelfEntryDeleted(removed);
+  }
+  if (!added.empty()) {
+    NotifySendTabToSelfEntryAdded(added);
   }
   return base::nullopt;
 }
@@ -175,14 +185,49 @@
 
   const SendTabToSelfEntry* result =
       entries_.emplace(guid, std::move(entry)).first->second.get();
-  NotifySendTabToSelfModelChanged();
+
+  NotifySendTabToSelfEntryAdded(std::vector<const SendTabToSelfEntry*>{result});
 
   return result;
 }
 
-void SendTabToSelfBridge::NotifySendTabToSelfModelChanged() {
-  for (SendTabToSelfModelObserver& observer : observers_)
-    observer.SendTabToSelfModelChanged();
+void SendTabToSelfBridge::DeleteEntry(const std::string& guid) {
+  // Assure that an entry with that guid exists.
+  if (GetEntryByGUID(guid) == nullptr) {
+    return;
+  }
+
+  DCHECK(change_processor()->IsTrackingMetadata());
+  syncer::InMemoryMetadataChangeList metadata_change_list;
+  change_processor()->Delete(guid, &metadata_change_list);
+
+  entries_.erase(guid);
+
+  NotifySendTabToSelfEntryDeleted(std::vector<std::string>{guid});
+}
+
+void SendTabToSelfBridge::DismissEntry(const std::string& guid) {
+  // Assure that an entry with that guid exists.
+  if (GetEntryByGUID(guid) == nullptr) {
+    return;
+  }
+
+  NOTIMPLEMENTED();
+  // TODO(jeffreycohen) Implement once there is local storage.
+}
+
+void SendTabToSelfBridge::NotifySendTabToSelfEntryAdded(
+    const std::vector<const SendTabToSelfEntry*>& new_entries) {
+  for (SendTabToSelfModelObserver& observer : observers_) {
+    observer.SendTabToSelfEntriesAdded(new_entries);
+  }
+}
+
+void SendTabToSelfBridge::NotifySendTabToSelfEntryDeleted(
+    const std::vector<std::string>& guids) {
+  for (SendTabToSelfModelObserver& observer : observers_) {
+    observer.SendTabToSelfEntriesRemoved(guids);
+  }
 }
 
 }  // namespace send_tab_to_self
diff --git a/components/send_tab_to_self/send_tab_to_self_bridge.h b/components/send_tab_to_self/send_tab_to_self_bridge.h
index 6e96ab3..4214f68 100644
--- a/components/send_tab_to_self/send_tab_to_self_bridge.h
+++ b/components/send_tab_to_self/send_tab_to_self_bridge.h
@@ -64,12 +64,20 @@
                                      const std::string& title,
                                      base::Time navigation_time) override;
 
+  void DeleteEntry(const std::string& guid) override;
+  void DismissEntry(const std::string& guid) override;
+
  private:
   using SendTabToSelfEntries =
       std::map<std::string, std::unique_ptr<SendTabToSelfEntry>>;
 
-  // Notify all observers of a change;
-  void NotifySendTabToSelfModelChanged();
+  // Notify all observers of added |entries| when the underlying model changes.
+  void NotifySendTabToSelfEntryAdded(
+      const std::vector<const SendTabToSelfEntry*>& new_entries);
+
+  // Notify all observers when the entries with |guids| have been removed from
+  // the model.
+  void NotifySendTabToSelfEntryDeleted(const std::vector<std::string>& guids);
 
   // |entries_| is keyed by GUIDs.
   SendTabToSelfEntries entries_;
diff --git a/components/send_tab_to_self/send_tab_to_self_model.h b/components/send_tab_to_self/send_tab_to_self_model.h
index 6ae0a69..9d8240a 100644
--- a/components/send_tab_to_self/send_tab_to_self_model.h
+++ b/components/send_tab_to_self/send_tab_to_self_model.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_MODEL_H_
 #define COMPONENTS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_MODEL_H_
 
+#include <string>
 #include <vector>
 
 #include "base/observer_list.h"
@@ -32,11 +33,20 @@
       const std::string& guid) const = 0;
 
   // Adds |url| at the top of the entries. The entry title will be a
-  // trimmed copy of |title|.
+  // trimmed copy of |title|. Allows clients to modify the state of the model
+  // as driven by user behaviors.
   virtual const SendTabToSelfEntry* AddEntry(const GURL& url,
                                              const std::string& title,
                                              base::Time navigation_time) = 0;
 
+  // Remove entry with |guid| from entries. Allows clients to modify the state
+  // of the  model as driven by user behaviors.
+  virtual void DeleteEntry(const std::string& guid) = 0;
+
+  // Dismiss entry with |guid| from entries. Allows clients to modify the state
+  // of the  model as driven by user behaviors.
+  virtual void DismissEntry(const std::string& guid) = 0;
+
   // Observer registration methods. The model will remove all observers upon
   // destruction automatically.
   void AddObserver(SendTabToSelfModelObserver* observer);
diff --git a/components/send_tab_to_self/send_tab_to_self_model_observer.h b/components/send_tab_to_self/send_tab_to_self_model_observer.h
index 54c471c..fdfda2c 100644
--- a/components/send_tab_to_self/send_tab_to_self_model_observer.h
+++ b/components/send_tab_to_self/send_tab_to_self_model_observer.h
@@ -5,8 +5,13 @@
 #ifndef COMPONENTS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_MODEL_OBSERVER_H_
 #define COMPONENTS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_MODEL_OBSERVER_H_
 
+#include <string>
+#include <vector>
+
 namespace send_tab_to_self {
 
+class SendTabToSelfEntry;
+
 // Observer for the Send Tab To Self model. In the observer methods care should
 // be taken to not modify the model.
 class SendTabToSelfModelObserver {
@@ -18,8 +23,13 @@
   // is unsafe to use the model.
   virtual void SendTabToSelfModelLoaded() = 0;
 
-  // Invoked when elements of the model are added, removed, or updated.
-  virtual void SendTabToSelfModelChanged() = 0;
+  // Invoked when elements of the model are added or removed. This is the
+  // mechanism for the sync server to push changes in the state of the model to
+  // clients.
+  virtual void SendTabToSelfEntriesAdded(
+      const std::vector<const SendTabToSelfEntry*>& new_entries) = 0;
+  virtual void SendTabToSelfEntriesRemoved(
+      const std::vector<std::string>& guids) = 0;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(SendTabToSelfModelObserver);
diff --git a/components/signin/core/browser/account_fetcher_service.cc b/components/signin/core/browser/account_fetcher_service.cc
index f0ce7e3..7095d89 100644
--- a/components/signin/core/browser/account_fetcher_service.cc
+++ b/components/signin/core/browser/account_fetcher_service.cc
@@ -41,6 +41,8 @@
 
 }  // namespace
 
+const char kImageFetcherUmaClient[] = "AccountFetcherService";
+
 // This pref used to be in the AccountTrackerService, hence its string value.
 const char AccountFetcherService::kLastUpdatePref[] =
     "account_tracker_service_last_update";
@@ -304,8 +306,10 @@
       picture_url, kAccountImageDownloadSize, true /* no_silhouette */));
   auto callback = base::BindRepeating(&AccountFetcherService::OnImageFetched,
                                       base::Unretained(this), account_id);
+  image_fetcher::ImageFetcherParams params(traffic_annotation,
+                                           kImageFetcherUmaClient);
   GetOrCreateImageFetcher()->FetchImage(image_url_with_size, callback,
-                                        traffic_annotation);
+                                        std::move(params));
 }
 
 void AccountFetcherService::OnUserInfoFetchFailure(
diff --git a/components/sync/driver/sync_session_durations_metrics_recorder.cc b/components/sync/driver/sync_session_durations_metrics_recorder.cc
index b112019..2988d6b 100644
--- a/components/sync/driver/sync_session_durations_metrics_recorder.cc
+++ b/components/sync/driver/sync_session_durations_metrics_recorder.cc
@@ -130,7 +130,7 @@
 }
 
 void SyncSessionDurationsMetricsRecorder::OnRefreshTokenUpdatedForAccount(
-    const AccountInfo& account_info) {
+    const CoreAccountInfo& account_info) {
   DVLOG(1) << __func__;
   HandleSyncAndAccountChange();
 }
diff --git a/components/sync/driver/sync_session_durations_metrics_recorder.h b/components/sync/driver/sync_session_durations_metrics_recorder.h
index 056152d..c8139605 100644
--- a/components/sync/driver/sync_session_durations_metrics_recorder.h
+++ b/components/sync/driver/sync_session_durations_metrics_recorder.h
@@ -40,7 +40,7 @@
 
   // IdentityManager::Observer:
   void OnRefreshTokenUpdatedForAccount(
-      const AccountInfo& account_info) override;
+      const CoreAccountInfo& account_info) override;
   void OnRefreshTokenRemovedForAccount(const std::string& account_id) override;
   void OnRefreshTokensLoaded() override;
   void OnErrorStateOfRefreshTokenUpdatedForAccount(
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 0bdcde6..078e20d 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1291,6 +1291,8 @@
     "permissions/permission_service_impl.h",
     "picture_in_picture/overlay_surface_embedder.cc",
     "picture_in_picture/overlay_surface_embedder.h",
+    "picture_in_picture/picture_in_picture_service_impl.cc",
+    "picture_in_picture/picture_in_picture_service_impl.h",
     "picture_in_picture/picture_in_picture_window_controller_impl.cc",
     "picture_in_picture/picture_in_picture_window_controller_impl.h",
     "portal/portal.cc",
diff --git a/content/browser/devtools/devtools_pipe_handler.cc b/content/browser/devtools/devtools_pipe_handler.cc
index 60c115b9..adb9950 100644
--- a/content/browser/devtools/devtools_pipe_handler.cc
+++ b/content/browser/devtools/devtools_pipe_handler.cc
@@ -13,19 +13,24 @@
 
 #include <stdio.h>
 #include <cstdlib>
+#include <memory>
 #include <string>
+#include <utility>
 #include "base/bind.h"
+#include "base/command_line.h"
 #include "base/files/file_util.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/message_loop/message_loop.h"
 #include "base/sequenced_task_runner.h"
 #include "base/single_thread_task_runner.h"
+#include "base/strings/string_util.h"
 #include "base/task/post_task.h"
 #include "base/threading/thread.h"
 #include "build/build_config.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/devtools_agent_host.h"
+#include "content/public/common/content_switches.h"
 #include "net/server/http_connection.h"
 
 const size_t kReceiveBufferSizeForDevTools = 100 * 1024 * 1024;  // 100Mb
@@ -35,64 +40,64 @@
 
 namespace content {
 
-namespace {
-
-const char kDevToolsPipeHandlerReadThreadName[] =
-    "DevToolsPipeHandlerReadThread";
-const char kDevToolsPipeHandlerWriteThreadName[] =
-    "DevToolsPipeHandlerWriteThread";
-
-void WriteIntoPipe(int write_fd, const std::string& message) {
-#if defined(OS_WIN)
-  HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(write_fd));
-#endif
-
-  size_t total_written = 0;
-  while (total_written < message.length()) {
-    size_t length = message.length() - total_written;
-    if (length > kWritePacketSize)
-      length = kWritePacketSize;
-#if defined(OS_WIN)
-    DWORD result = 0;
-    WriteFile(handle, message.data() + total_written,
-              static_cast<DWORD>(length), &result, nullptr);
-#else
-    int result = write(write_fd, message.data() + total_written, length);
-#endif
-    if (!result) {
-      LOG(ERROR) << "Could not write into pipe";
-      return;
-    }
-    total_written += result;
-  }
-#if defined(OS_WIN)
-  DWORD result = 0;
-  WriteFile(handle, "\0", 1, &result, nullptr);
-#else
-  int result = write(write_fd, "\0", 1);
-#endif
-  if (!result) {
-    LOG(ERROR) << "Could not write into pipe";
-    return;
-  }
-}
-
-}  // namespace
-
-// PipeReader ------------------------------------------------------------------
-
-class PipeReader {
+class PipeReaderBase {
  public:
-  PipeReader(base::WeakPtr<DevToolsPipeHandler> devtools_handler, int read_fd);
-  ~PipeReader() = default;
-  void ReadLoop();
+  explicit PipeReaderBase(base::WeakPtr<DevToolsPipeHandler> devtools_handler,
+                          int read_fd)
+      : devtools_handler_(std::move(devtools_handler)) {
+#if defined(OS_WIN)
+    read_handle_ = reinterpret_cast<HANDLE>(_get_osfhandle(read_fd));
+#else
+    read_fd_ = read_fd;
+#endif
+  }
 
- private:
-  bool HandleReadResult(int result);
+  virtual ~PipeReaderBase() = default;
 
-  void ConnectionClosed();
+  void ReadLoop() {
+    ReadLoopInternal();
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::UI},
+        base::BindOnce(&DevToolsPipeHandler::Shutdown, devtools_handler_));
+  }
 
-  scoped_refptr<net::HttpConnection::ReadIOBuffer> read_buffer_;
+ protected:
+  virtual void ReadLoopInternal() = 0;
+
+  size_t ReadBytes(void* buffer, size_t size, bool exact_size) {
+    size_t bytes_read = 0;
+    while (bytes_read < size) {
+#if defined(OS_WIN)
+      DWORD size_read = 0;
+      bool had_error =
+          !ReadFile(read_handle_, static_cast<char*>(buffer) + bytes_read,
+                    size - bytes_read, &size_read, nullptr);
+#else
+      int size_read = read(read_fd_, static_cast<char*>(buffer) + bytes_read,
+                           size - bytes_read);
+      if (size_read < 0 && errno == EINTR)
+        continue;
+      bool had_error = size_read <= 0;
+#endif
+      if (had_error) {
+        LOG(ERROR) << "Connection terminated while reading from pipe";
+        return 0;
+      }
+      bytes_read += size_read;
+      if (!exact_size)
+        break;
+    }
+    return bytes_read;
+  }
+
+  void HandleMessage(std::string buffer) {
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::UI},
+        base::BindOnce(&DevToolsPipeHandler::HandleMessage, devtools_handler_,
+                       std::move(buffer)));
+  }
+
+ protected:
   base::WeakPtr<DevToolsPipeHandler> devtools_handler_;
 #if defined(OS_WIN)
   HANDLE read_handle_;
@@ -101,75 +106,153 @@
 #endif
 };
 
-PipeReader::PipeReader(base::WeakPtr<DevToolsPipeHandler> devtools_handler,
-                       int read_fd)
-    : devtools_handler_(devtools_handler) {
+namespace {
+
+const char kDevToolsPipeHandlerReadThreadName[] =
+    "DevToolsPipeHandlerReadThread";
+const char kDevToolsPipeHandlerWriteThreadName[] =
+    "DevToolsPipeHandlerWriteThread";
+
+// Our CBOR (RFC 7049) based format starts with a tag 24 indicating
+// an envelope, that is, a byte string which as payload carries the
+// entire remaining message. Thereby, the length of the byte string
+// also tells us the message size on the wire.
+// Envelope is encoded as TAG with minor info 24.
+// Our byte strings always have their length encoded as a 32 bit
+// unsigned value.
+
+constexpr uint8_t kCBOR_MAJOR_BYTE_STRING = 2;
+constexpr uint8_t kCBOR_MAJOR_TAG = 6;
+
+constexpr uint8_t kCBOR_MINOR_ENVELOPE = 24;
+constexpr uint8_t kCBOR_MINOR_32BIT = 26;
+
+constexpr uint8_t cbor_first_byte(uint8_t major, uint8_t minor) {
+  return major << 5 | minor;
+}
+
+constexpr uint8_t kCBOR_ENVELOPE_TAG =
+    cbor_first_byte(kCBOR_MAJOR_TAG, kCBOR_MINOR_ENVELOPE);
+constexpr uint8_t kCBOR_BYTESTR_32B_LEN =
+    cbor_first_byte(kCBOR_MAJOR_BYTE_STRING, kCBOR_MINOR_32BIT);
+
+void WriteBytes(int write_fd, const char* bytes, size_t size) {
 #if defined(OS_WIN)
-  read_handle_ = reinterpret_cast<HANDLE>(_get_osfhandle(read_fd));
-#else
-  read_fd_ = read_fd;
+  HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(write_fd));
 #endif
 
-  read_buffer_ = new net::HttpConnection::ReadIOBuffer();
-  read_buffer_->set_max_buffer_size(kReceiveBufferSizeForDevTools);
-}
-
-void PipeReader::ReadLoop() {
-  while (true) {
-    if (read_buffer_->RemainingCapacity() == 0 &&
-        !read_buffer_->IncreaseCapacity()) {
-      LOG(ERROR) << "Connection closed, not enough capacity";
-      break;
-    }
-
+  size_t total_written = 0;
+  while (total_written < size) {
+    size_t length = size - total_written;
+    if (length > kWritePacketSize)
+      length = kWritePacketSize;
 #if defined(OS_WIN)
-    DWORD result = 0;
-    ReadFile(read_handle_, read_buffer_->data(),
-             read_buffer_->RemainingCapacity(), &result, nullptr);
+    DWORD bytes_written = 0;
+    bool had_error =
+        !WriteFile(handle, bytes + total_written, static_cast<DWORD>(length),
+                   &bytes_written, nullptr);
 #else
-    int result =
-        read(read_fd_, read_buffer_->data(), read_buffer_->RemainingCapacity());
+    int bytes_written = write(write_fd, bytes + total_written, length);
+    if (bytes_written < 0 && errno == EINTR)
+      continue;
+    bool had_error = bytes_written <= 0;
 #endif
-
-    if (!HandleReadResult(result))
-      break;
+    if (had_error) {
+      LOG(ERROR) << "Could not write into pipe";
+      return;
+    }
+    total_written += bytes_written;
   }
-
-  ConnectionClosed();
 }
 
-bool PipeReader::HandleReadResult(int result) {
-  if (result == 0) {
-    LOG(ERROR) << "Connection terminated while reading from pipe";
-    return false;
+void WriteIntoPipeASCIIZ(int write_fd, const std::string& message) {
+  WriteBytes(write_fd, message.data(), message.size());
+  WriteBytes(write_fd, "\0", 1);
+}
+
+void WriteIntoPipeCBOR(int write_fd, const std::string& message) {
+  DCHECK(!message.empty() &&
+         static_cast<uint8_t>(message[0]) == kCBOR_ENVELOPE_TAG);
+
+  WriteBytes(write_fd, message.data(), message.size());
+}
+
+class PipeReaderASCIIZ : public PipeReaderBase {
+ public:
+  PipeReaderASCIIZ(base::WeakPtr<DevToolsPipeHandler> devtools_handler,
+                   int read_fd)
+      : PipeReaderBase(std::move(devtools_handler), read_fd) {
+    read_buffer_ = new net::HttpConnection::ReadIOBuffer();
+    read_buffer_->set_max_buffer_size(kReceiveBufferSizeForDevTools);
   }
 
-  read_buffer_->DidRead(result);
+ private:
+  void ReadLoopInternal() override {
+    while (true) {
+      if (read_buffer_->RemainingCapacity() == 0 &&
+          !read_buffer_->IncreaseCapacity()) {
+        LOG(ERROR) << "Connection closed, not enough capacity";
+        break;
+      }
 
-  // Go over the last read chunk, look for \0, extract messages.
-  int offset = 0;
-  for (int i = read_buffer_->GetSize() - result; i < read_buffer_->GetSize();
-       ++i) {
-    if (read_buffer_->StartOfBuffer()[i] == '\0') {
-      std::string str(read_buffer_->StartOfBuffer() + offset, i - offset);
+      size_t bytes_read = ReadBytes(read_buffer_->data(),
+                                    read_buffer_->RemainingCapacity(), false);
+      if (!bytes_read)
+        break;
+      read_buffer_->DidRead(bytes_read);
 
-      base::PostTaskWithTraits(
-          FROM_HERE, {BrowserThread::UI},
-          base::BindOnce(&DevToolsPipeHandler::HandleMessage, devtools_handler_,
-                         std::move(str)));
-      offset = i + 1;
+      // Go over the last read chunk, look for \0, extract messages.
+      int offset = 0;
+      for (int i = read_buffer_->GetSize() - bytes_read;
+           i < read_buffer_->GetSize(); ++i) {
+        if (read_buffer_->StartOfBuffer()[i] == '\0') {
+          std::string str(read_buffer_->StartOfBuffer() + offset, i - offset);
+          HandleMessage(std::move(str));
+          offset = i + 1;
+        }
+      }
+      if (offset)
+        read_buffer_->DidConsume(offset);
     }
   }
-  if (offset)
-    read_buffer_->DidConsume(offset);
-  return true;
-}
 
-void PipeReader::ConnectionClosed() {
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::UI},
-      base::BindOnce(&DevToolsPipeHandler::Shutdown, devtools_handler_));
-}
+  scoped_refptr<net::HttpConnection::ReadIOBuffer> read_buffer_;
+};
+
+class PipeReaderCBOR : public PipeReaderBase {
+ public:
+  PipeReaderCBOR(base::WeakPtr<DevToolsPipeHandler> devtools_handler,
+                 int read_fd)
+      : PipeReaderBase(std::move(devtools_handler), read_fd) {}
+
+ private:
+  static uint32_t UInt32FromCBOR(const uint8_t* buf) {
+    return (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3];
+  }
+
+  void ReadLoopInternal() override {
+    while (true) {
+      const size_t kHeaderSize = 6;  // tag? type length*4
+      std::string buffer(kHeaderSize, '\0');
+      if (!ReadBytes(&buffer.front(), kHeaderSize, true))
+        break;
+      const uint8_t* prefix = reinterpret_cast<const uint8_t*>(buffer.data());
+      if (prefix[0] != kCBOR_ENVELOPE_TAG ||
+          prefix[1] != kCBOR_BYTESTR_32B_LEN) {
+        LOG(ERROR) << "Unexpected start of CBOR envelope " << prefix[0] << ","
+                   << prefix[1];
+        return;
+      }
+      uint32_t msg_size = UInt32FromCBOR(prefix + 2);
+      buffer.resize(kHeaderSize + msg_size, '\0');
+      if (!ReadBytes(&buffer.front() + kHeaderSize, msg_size, true))
+        return;
+      HandleMessage(std::move(buffer));
+    }
+  }
+};
+
+}  // namespace
 
 // DevToolsPipeHandler ---------------------------------------------------
 
@@ -195,11 +278,26 @@
       nullptr, DevToolsAgentHost::CreateServerSocketCallback());
   browser_target_->AttachClient(this);
 
-  pipe_reader_.reset(new PipeReader(weak_factory_.GetWeakPtr(), read_fd_));
-  base::TaskRunner* task_runner = read_thread_->task_runner().get();
-  task_runner->PostTask(FROM_HERE,
-                        base::BindOnce(&PipeReader::ReadLoop,
-                                       base::Unretained(pipe_reader_.get())));
+  std::string str_mode = base::ToLowerASCII(
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          switches::kRemoteDebuggingPipe));
+  mode_ = str_mode == "cbor" ? DevToolsPipeHandler::ProtocolMode::kCBOR
+                             : DevToolsPipeHandler::ProtocolMode::kASCIIZ;
+
+  switch (mode_) {
+    case ProtocolMode::kASCIIZ:
+      pipe_reader_ = std::make_unique<PipeReaderASCIIZ>(
+          weak_factory_.GetWeakPtr(), read_fd_);
+      break;
+
+    case ProtocolMode::kCBOR:
+      pipe_reader_ = std::make_unique<PipeReaderCBOR>(
+          weak_factory_.GetWeakPtr(), read_fd_);
+      break;
+  }
+  read_thread_->task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&PipeReaderBase::ReadLoop,
+                                base::Unretained(pipe_reader_.get())));
 }
 
 void DevToolsPipeHandler::Shutdown() {
@@ -238,7 +336,7 @@
 
   // Post PipeReader and WeakPtr factory destruction on the reader thread.
   read_thread_->task_runner()->PostTask(
-      FROM_HERE, base::BindOnce([](PipeReader* reader) { delete reader; },
+      FROM_HERE, base::BindOnce([](PipeReaderBase* reader) { delete reader; },
                                 pipe_reader_.release()));
 
   // Post background task that would join and destroy the threads.
@@ -269,7 +367,10 @@
     return;
   base::TaskRunner* task_runner = write_thread_->task_runner().get();
   task_runner->PostTask(
-      FROM_HERE, base::BindOnce(&WriteIntoPipe, write_fd_, std::move(message)));
+      FROM_HERE,
+      base::BindOnce(mode_ == ProtocolMode::kASCIIZ ? WriteIntoPipeASCIIZ
+                                                    : WriteIntoPipeCBOR,
+                     write_fd_, std::move(message)));
 }
 
 void DevToolsPipeHandler::AgentHostClosed(DevToolsAgentHost* agent_host) {}
diff --git a/content/browser/devtools/devtools_pipe_handler.h b/content/browser/devtools/devtools_pipe_handler.h
index 0fe262e..f09fa58 100644
--- a/content/browser/devtools/devtools_pipe_handler.h
+++ b/content/browser/devtools/devtools_pipe_handler.h
@@ -15,7 +15,7 @@
 
 namespace content {
 
-class PipeReader;
+class PipeReaderBase;
 
 class DevToolsPipeHandler : public DevToolsAgentHostClient {
  public:
@@ -33,7 +33,16 @@
   void Shutdown();
 
  private:
-  std::unique_ptr<PipeReader> pipe_reader_;
+  enum class ProtocolMode {
+    // Legacy text protocol format with messages separated by \0's.
+    kASCIIZ,
+    // Experimental (!) CBOR (RFC 7049) based binary format.
+    kCBOR
+  };
+
+  ProtocolMode mode_;
+
+  std::unique_ptr<PipeReaderBase> pipe_reader_;
   std::unique_ptr<base::Thread> read_thread_;
   std::unique_ptr<base::Thread> write_thread_;
   scoped_refptr<DevToolsAgentHost> browser_target_;
diff --git a/content/browser/devtools/devtools_session.h b/content/browser/devtools/devtools_session.h
index 8d74ef9..413d757 100644
--- a/content/browser/devtools/devtools_session.h
+++ b/content/browser/devtools/devtools_session.h
@@ -6,6 +6,8 @@
 #define CONTENT_BROWSER_DEVTOOLS_DEVTOOLS_SESSION_H_
 
 #include <map>
+#include <string>
+#include <vector>
 
 #include "base/containers/flat_map.h"
 #include "base/memory/weak_ptr.h"
diff --git a/content/browser/devtools/protocol/input_handler.cc b/content/browser/devtools/protocol/input_handler.cc
index b3ab15a..cad49d8 100644
--- a/content/browser/devtools/protocol/input_handler.cc
+++ b/content/browser/devtools/protocol/input_handler.cc
@@ -14,6 +14,7 @@
 #include "content/browser/devtools/devtools_agent_host_impl.h"
 #include "content/browser/devtools/protocol/native_input_event_builder.h"
 #include "content/browser/frame_host/render_frame_host_impl.h"
+#include "content/browser/renderer_host/input/synthetic_pointer_action.h"
 #include "content/browser/renderer_host/input/touch_emulator.h"
 #include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/renderer_host/render_widget_host_input_event_router.h"
@@ -165,18 +166,6 @@
   return blink::WebInputEvent::kUndefined;
 }
 
-blink::WebInputEvent::Type GetTouchEventType(const std::string& type) {
-  if (type == Input::DispatchTouchEvent::TypeEnum::TouchStart)
-    return blink::WebInputEvent::kTouchStart;
-  if (type == Input::DispatchTouchEvent::TypeEnum::TouchEnd)
-    return blink::WebInputEvent::kTouchEnd;
-  if (type == Input::DispatchTouchEvent::TypeEnum::TouchMove)
-    return blink::WebInputEvent::kTouchMove;
-  if (type == Input::DispatchTouchEvent::TypeEnum::TouchCancel)
-    return blink::WebInputEvent::kTouchCancel;
-  return blink::WebInputEvent::kUndefined;
-}
-
 blink::WebPointerProperties::PointerType GetPointerType(
     const std::string& type) {
   if (type == Input::DispatchMouseEvent::PointerTypeEnum::Mouse)
@@ -186,36 +175,32 @@
   return blink::WebPointerProperties::PointerType::kMouse;
 }
 
-bool GenerateTouchPoints(
-    blink::WebTouchEvent* event,
-    blink::WebInputEvent::Type type,
-    const base::flat_map<int, blink::WebTouchPoint>& points,
-    const blink::WebTouchPoint& changing) {
-  event->touches_length = 1;
-  event->touches[0] = changing;
-  for (auto& it : points) {
-    if (it.first == changing.id)
-      continue;
-    if (event->touches_length == blink::WebTouchEvent::kTouchesLengthCap)
-      return false;
-    event->touches[event->touches_length] = it.second;
-    event->touches[event->touches_length].state =
-        blink::WebTouchPoint::kStateStationary;
-    event->touches_length++;
-  }
-  if (type != blink::WebInputEvent::kUndefined) {
-    event->touches[0].state = type == blink::WebInputEvent::kTouchCancel
-                                  ? blink::WebTouchPoint::kStateCancelled
-                                  : blink::WebTouchPoint::kStateReleased;
-    event->SetType(type);
-  } else if (points.find(changing.id) == points.end()) {
-    event->touches[0].state = blink::WebTouchPoint::kStatePressed;
-    event->SetType(blink::WebInputEvent::kTouchStart);
-  } else {
-    event->touches[0].state = blink::WebTouchPoint::kStateMoved;
-    event->SetType(blink::WebInputEvent::kTouchMove);
-  }
-  return true;
+SyntheticPointerActionParams::PointerActionType GetTouchPointerActionType(
+    const std::string& type) {
+  if (type == Input::DispatchTouchEvent::TypeEnum::TouchStart)
+    return SyntheticPointerActionParams::PointerActionType::PRESS;
+  if (type == Input::DispatchTouchEvent::TypeEnum::TouchEnd)
+    return SyntheticPointerActionParams::PointerActionType::RELEASE;
+  if (type == Input::DispatchTouchEvent::TypeEnum::TouchMove)
+    return SyntheticPointerActionParams::PointerActionType::MOVE;
+  if (type == Input::DispatchTouchEvent::TypeEnum::TouchCancel)
+    return SyntheticPointerActionParams::PointerActionType::CANCEL;
+  return SyntheticPointerActionParams::PointerActionType::NOT_INITIALIZED;
+}
+
+SyntheticPointerActionParams::Button GetPointerActionParamsButton(
+    const std::string& button) {
+  if (button == Input::DispatchMouseEvent::ButtonEnum::Left)
+    return SyntheticPointerActionParams::Button::LEFT;
+  if (button == Input::DispatchMouseEvent::ButtonEnum::Middle)
+    return SyntheticPointerActionParams::Button::MIDDLE;
+  if (button == Input::DispatchMouseEvent::ButtonEnum::Right)
+    return SyntheticPointerActionParams::Button::RIGHT;
+  if (button == Input::DispatchMouseEvent::ButtonEnum::Back)
+    return SyntheticPointerActionParams::Button::BACK;
+  if (button == Input::DispatchMouseEvent::ButtonEnum::Forward)
+    return SyntheticPointerActionParams::Button::FORWARD;
+  return SyntheticPointerActionParams::Button::NO_BUTTON;
 }
 
 void SendSynthesizePinchGestureResponse(
@@ -272,6 +257,17 @@
   }
 }
 
+void DispatchPointerActionsResponse(
+    std::unique_ptr<Input::Backend::DispatchTouchEventCallback> callback,
+    SyntheticGesture::Result result) {
+  if (result == SyntheticGesture::Result::GESTURE_FINISHED) {
+    callback->sendSuccess();
+  } else {
+    callback->sendFailure(Response::Error(
+        base::StringPrintf("Action sequence failed, result was %d", result)));
+  }
+}
+
 }  // namespace
 
 class InputHandler::InputInjector
@@ -478,7 +474,7 @@
   if (web_contents && ignore_input_events_)
     web_contents->SetIgnoreInputEvents(false);
   ignore_input_events_ = false;
-  touch_points_.clear();
+  pointer_ids_.clear();
   return Response::OK();
 }
 
@@ -680,8 +676,15 @@
     protocol::Maybe<int> maybe_modifiers,
     protocol::Maybe<double> maybe_timestamp,
     std::unique_ptr<DispatchTouchEventCallback> callback) {
-  blink::WebInputEvent::Type type = GetTouchEventType(event_type);
-  if (type == blink::WebInputEvent::kUndefined) {
+  if (!host_ || !host_->GetRenderWidgetHost()) {
+    callback->sendFailure(Response::InternalError());
+    return;
+  }
+
+  SyntheticPointerActionParams::PointerActionType pointer_action_type =
+      GetTouchPointerActionType(event_type);
+  if (pointer_action_type ==
+      SyntheticPointerActionParams::PointerActionType::NOT_INITIALIZED) {
     callback->sendFailure(Response::InvalidParams(
         base::StringPrintf("Unexpected event type '%s'", event_type.c_str())));
     return;
@@ -690,50 +693,81 @@
   int modifiers = GetEventModifiers(
       maybe_modifiers.fromMaybe(blink::WebInputEvent::kNoModifiers), false,
       false, 0, 0);
-  base::TimeTicks timestamp = GetEventTimeTicks(maybe_timestamp);
 
-  if ((type == blink::WebInputEvent::kTouchStart ||
-       type == blink::WebInputEvent::kTouchMove) &&
+  if ((pointer_action_type ==
+           SyntheticPointerActionParams::PointerActionType::PRESS ||
+       pointer_action_type ==
+           SyntheticPointerActionParams::PointerActionType::MOVE) &&
       touch_points->length() == 0) {
     callback->sendFailure(Response::InvalidParams(
         "TouchStart and TouchMove must have at least one touch point."));
     return;
   }
-  if ((type == blink::WebInputEvent::kTouchEnd ||
-       type == blink::WebInputEvent::kTouchCancel) &&
+  if ((pointer_action_type ==
+           SyntheticPointerActionParams::PointerActionType::RELEASE ||
+       pointer_action_type ==
+           SyntheticPointerActionParams::PointerActionType::CANCEL) &&
       touch_points->length() > 0) {
     callback->sendFailure(Response::InvalidParams(
         "TouchEnd and TouchCancel must not have any touch points."));
     return;
   }
-  if (type == blink::WebInputEvent::kTouchStart && !touch_points_.empty()) {
+  if (pointer_action_type ==
+          SyntheticPointerActionParams::PointerActionType::PRESS &&
+      !pointer_ids_.empty()) {
     callback->sendFailure(Response::InvalidParams(
         "Must have no prior active touch points to start a new touch."));
     return;
   }
-  if (type != blink::WebInputEvent::kTouchStart && touch_points_.empty()) {
+  if (pointer_action_type !=
+          SyntheticPointerActionParams::PointerActionType::PRESS &&
+      pointer_ids_.empty()) {
     callback->sendFailure(Response::InvalidParams(
         "Must send a TouchStart first to start a new touch."));
     return;
   }
 
-  base::flat_map<int, blink::WebTouchPoint> points;
+  SyntheticGestureParams::GestureSourceType gesture_source_type =
+      SyntheticGestureParams::GestureSourceType::TOUCH_INPUT;
+  SyntheticPointerActionListParams action_list_params;
+  SyntheticPointerActionListParams::ParamList param_list;
+  action_list_params.gesture_source_type = gesture_source_type;
+  if (pointer_action_type ==
+          SyntheticPointerActionParams::PointerActionType::RELEASE ||
+      pointer_action_type ==
+          SyntheticPointerActionParams::PointerActionType::CANCEL) {
+    for (auto it = pointer_ids_.begin(); it != pointer_ids_.end();) {
+      SyntheticPointerActionParams action_params =
+          PrepareSyntheticPointerActionParams(pointer_action_type, *it, "", 0,
+                                              0, modifiers);
+      param_list.push_back(action_params);
+      it = pointer_ids_.erase(it);
+    }
+  }
+
   size_t with_id = 0;
+  gfx::PointF original;
+  std::set<int> current_pointer_ids;
   for (size_t i = 0; i < touch_points->length(); ++i) {
     Input::TouchPoint* point = touch_points->get(i);
     int id = point->GetId(i);
     if (point->HasId())
       with_id++;
-    points[id].id = id;
-    points[id].radius_x = point->GetRadiusX(1.0);
-    points[id].radius_y = point->GetRadiusY(1.0);
-    points[id].rotation_angle = point->GetRotationAngle(0.0);
-    points[id].force = point->GetForce(1.0);
-    points[id].pointer_type = blink::WebPointerProperties::PointerType::kTouch;
-    points[id].SetPositionInWidget(point->GetX() * page_scale_factor_,
-                                   point->GetY() * page_scale_factor_);
-    points[id].SetPositionInScreen(point->GetX() * page_scale_factor_,
-                                   point->GetY() * page_scale_factor_);
+
+    SyntheticPointerActionParams::PointerActionType action_type =
+        SyntheticPointerActionParams::PointerActionType::MOVE;
+    if (pointer_ids_.find(id) == pointer_ids_.end()) {
+      pointer_ids_.insert(id);
+      action_type = SyntheticPointerActionParams::PointerActionType::PRESS;
+    }
+    SyntheticPointerActionParams action_params =
+        PrepareSyntheticPointerActionParams(
+            action_type, id, "", point->GetX(), point->GetY(), modifiers,
+            point->GetRadiusX(1.0), point->GetRadiusY(1.0),
+            point->GetRotationAngle(0.0), point->GetForce(1.0));
+    param_list.push_back(action_params);
+    original = gfx::PointF(point->GetX(), point->GetY());
+    current_pointer_ids.insert(id);
   }
   if (with_id > 0 && with_id < touch_points->length()) {
     callback->sendFailure(Response::InvalidParams(
@@ -741,52 +775,32 @@
     return;
   }
 
-  std::vector<blink::WebTouchEvent> events;
-  bool ok = true;
-  for (auto& id_point : points) {
-    if (touch_points_.find(id_point.first) != touch_points_.end())
-      continue;
-    events.emplace_back(type, modifiers, timestamp);
-    ok &= GenerateTouchPoints(&events.back(), blink::WebInputEvent::kUndefined,
-                              touch_points_, id_point.second);
-    touch_points_.insert(id_point);
-  }
-  for (auto& id_point : points) {
-    DCHECK(touch_points_.find(id_point.first) != touch_points_.end());
-    if (touch_points_[id_point.first].PositionInWidget() ==
-        id_point.second.PositionInWidget()) {
-      continue;
+  if (pointer_action_type ==
+          SyntheticPointerActionParams::PointerActionType::MOVE &&
+      current_pointer_ids.size() < pointer_ids_.size()) {
+    for (auto it = pointer_ids_.begin(); it != pointer_ids_.end();) {
+      if (current_pointer_ids.find(*it) != current_pointer_ids.end()) {
+        it++;
+        continue;
+      }
+      SyntheticPointerActionParams action_params =
+          PrepareSyntheticPointerActionParams(
+              SyntheticPointerActionParams::PointerActionType::RELEASE, *it, "",
+              0, 0, modifiers);
+      param_list.push_back(action_params);
+      it = pointer_ids_.erase(it);
     }
-    events.emplace_back(type, modifiers, timestamp);
-    ok &= GenerateTouchPoints(&events.back(), blink::WebInputEvent::kUndefined,
-                              touch_points_, id_point.second);
-    touch_points_[id_point.first] = id_point.second;
   }
-  if (type != blink::WebInputEvent::kTouchCancel)
-    type = blink::WebInputEvent::kTouchEnd;
-  for (auto it = touch_points_.begin(); it != touch_points_.end();) {
-    if (points.find(it->first) != points.end()) {
-      it++;
-      continue;
-    }
-    events.emplace_back(type, modifiers, timestamp);
-    ok &= GenerateTouchPoints(&events.back(), type, touch_points_, it->second);
-    it = touch_points_.erase(it);
-  }
-  if (!ok) {
-    callback->sendFailure(Response::Error(
-        base::StringPrintf("Exceeded maximum touch points limit of %d",
-                           blink::WebTouchEvent::kTouchesLengthCap)));
-    return;
-  }
+  action_list_params.PushPointerActionParamsList(param_list);
 
-  if (events.empty()) {
-    callback->sendSuccess();
-    return;
+  if (!synthetic_pointer_driver_) {
+    synthetic_pointer_driver_ =
+        SyntheticPointerDriver::Create(gesture_source_type);
   }
+  std::unique_ptr<SyntheticPointerAction> synthetic_gesture =
+      std::make_unique<SyntheticPointerAction>(action_list_params);
+  synthetic_gesture->SetSyntheticPointerDriver(synthetic_pointer_driver_.get());
 
-  gfx::PointF original(events[0].touches[0].PositionInWidget().x,
-                       events[0].touches[0].PositionInWidget().y);
   gfx::PointF transformed;
   RenderWidgetHostImpl* widget_host =
       FindTargetWidgetHost(original, &transformed);
@@ -794,24 +808,58 @@
     callback->sendFailure(Response::InternalError());
     return;
   }
-  gfx::Vector2dF delta = transformed - original;
-  for (size_t i = 0; i < events.size(); i++) {
-    events[i].dispatch_type =
-        events[i].GetType() == blink::WebInputEvent::kTouchCancel
-            ? blink::WebInputEvent::kEventNonBlocking
-            : blink::WebInputEvent::kBlocking;
-    events[i].moved_beyond_slop_region = true;
-    events[i].unique_touch_event_id = ui::GetNextTouchEventId();
-    for (unsigned j = 0; j < events[i].touches_length; j++) {
-      blink::WebFloatPoint point = events[i].touches[j].PositionInWidget();
-      events[i].touches[j].SetPositionInWidget(point.x + delta.x(),
-                                               point.y + delta.y());
-      point = events[i].touches[j].PositionInScreen();
-      events[i].touches[j].SetPositionInScreen(point.x + delta.x(),
-                                               point.y + delta.y());
-    }
+  widget_host->QueueSyntheticGesture(
+      std::move(synthetic_gesture),
+      base::BindOnce(&DispatchPointerActionsResponse, std::move(callback)));
+}
+
+SyntheticPointerActionParams InputHandler::PrepareSyntheticPointerActionParams(
+    SyntheticPointerActionParams::PointerActionType pointer_action_type,
+    int id,
+    const std::string& button_name,
+    double x,
+    double y,
+    int key_modifiers,
+    float radius_x,
+    float radius_y,
+    float rotation_angle,
+    float force) {
+  SyntheticPointerActionParams action_params(pointer_action_type);
+  action_params.set_pointer_id(id);
+  SyntheticPointerActionParams::Button button =
+      GetPointerActionParamsButton(button_name);
+  switch (pointer_action_type) {
+    case SyntheticPointerActionParams::PointerActionType::PRESS:
+      action_params.set_position(
+          gfx::PointF(x * page_scale_factor_, y * page_scale_factor_));
+      action_params.set_button(button);
+      action_params.set_key_modifiers(key_modifiers);
+      action_params.set_width(radius_x * 2.f);
+      action_params.set_height(radius_y * 2.f);
+      action_params.set_rotation_angle(rotation_angle);
+      action_params.set_force(force);
+      break;
+    case SyntheticPointerActionParams::PointerActionType::MOVE:
+      action_params.set_position(
+          gfx::PointF(x * page_scale_factor_, y * page_scale_factor_));
+      action_params.set_key_modifiers(key_modifiers);
+      action_params.set_width(radius_x * 2.f);
+      action_params.set_height(radius_y * 2.f);
+      action_params.set_rotation_angle(rotation_angle);
+      action_params.set_force(force);
+      break;
+    case SyntheticPointerActionParams::PointerActionType::RELEASE:
+    case SyntheticPointerActionParams::PointerActionType::CANCEL:
+      action_params.set_button(button);
+      action_params.set_key_modifiers(key_modifiers);
+      break;
+    case SyntheticPointerActionParams::PointerActionType::LEAVE:
+    case SyntheticPointerActionParams::PointerActionType::IDLE:
+    case SyntheticPointerActionParams::PointerActionType::NOT_INITIALIZED:
+      NOTREACHED();
+      break;
   }
-  EnsureInjector(widget_host)->InjectTouchEvents(events, std::move(callback));
+  return action_params;
 }
 
 Response InputHandler::EmulateTouchFromMouseEvent(const std::string& type,
@@ -1132,7 +1180,7 @@
   while (!injectors_.empty())
     (*injectors_.begin())->Cleanup();
   // TODO(dgozman): cleanup touch callbacks as well?
-  touch_points_.clear();
+  pointer_ids_.clear();
 }
 
 bool InputHandler::PointIsWithinContents(gfx::PointF point) const {
diff --git a/content/browser/devtools/protocol/input_handler.h b/content/browser/devtools/protocol/input_handler.h
index 0d9f46d..f507982 100644
--- a/content/browser/devtools/protocol/input_handler.h
+++ b/content/browser/devtools/protocol/input_handler.h
@@ -5,6 +5,9 @@
 #ifndef CONTENT_BROWSER_DEVTOOLS_PROTOCOL_INPUT_HANDLER_H_
 #define CONTENT_BROWSER_DEVTOOLS_PROTOCOL_INPUT_HANDLER_H_
 
+#include <memory>
+#include <set>
+
 #include "base/containers/circular_deque.h"
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
@@ -14,6 +17,8 @@
 #include "content/browser/devtools/protocol/devtools_domain_handler.h"
 #include "content/browser/devtools/protocol/input.h"
 #include "content/browser/renderer_host/input/synthetic_gesture.h"
+#include "content/browser/renderer_host/input/synthetic_pointer_driver.h"
+#include "content/common/input/synthetic_pointer_action_list_params.h"
 #include "content/common/input/synthetic_smooth_scroll_gesture_params.h"
 #include "content/public/browser/render_widget_host.h"
 #include "third_party/blink/public/platform/web_input_event.h"
@@ -126,6 +131,18 @@
  private:
   class InputInjector;
 
+  SyntheticPointerActionParams PrepareSyntheticPointerActionParams(
+      SyntheticPointerActionParams::PointerActionType pointer_action_type,
+      int id,
+      const std::string& button_name,
+      double x,
+      double y,
+      int key_modifiers,
+      float radius_x = 1.f,
+      float radius_y = 1.f,
+      float rotation_angle = 0.f,
+      float force = 1.f);
+
   void SynthesizeRepeatingScroll(
       base::WeakPtr<RenderWidgetHostImpl> widget_host,
       SyntheticSmoothScrollGestureParams gesture_params,
@@ -157,7 +174,8 @@
   float page_scale_factor_;
   int last_id_;
   bool ignore_input_events_ = false;
-  base::flat_map<int, blink::WebTouchPoint> touch_points_;
+  std::set<int> pointer_ids_;
+  std::unique_ptr<SyntheticPointerDriver> synthetic_pointer_driver_;
   base::WeakPtrFactory<InputHandler> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(InputHandler);
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index fb5cebf..66ce76d 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -73,6 +73,7 @@
 #include "content/browser/permissions/permission_controller_impl.h"
 #include "content/browser/permissions/permission_service_context.h"
 #include "content/browser/permissions/permission_service_impl.h"
+#include "content/browser/picture_in_picture/picture_in_picture_service_impl.h"
 #include "content/browser/portal/portal.h"
 #include "content/browser/presentation/presentation_service_impl.h"
 #include "content/browser/quota_dispatcher_host.h"
@@ -4087,6 +4088,9 @@
 
   registry_->AddInterface(base::BindRepeating(&WakeLockServiceImpl::Create,
                                               base::Unretained(this)));
+
+  registry_->AddInterface(base::BindRepeating(
+      &PictureInPictureServiceImpl::Create, base::Unretained(this)));
 }
 
 void RenderFrameHostImpl::ResetWaitingState() {
diff --git a/content/browser/media/capture/fake_video_capture_stack.h b/content/browser/media/capture/fake_video_capture_stack.h
index 8092af1..975e0c7 100644
--- a/content/browser/media/capture/fake_video_capture_stack.h
+++ b/content/browser/media/capture/fake_video_capture_stack.h
@@ -17,7 +17,7 @@
 namespace media {
 class VideoFrame;
 class VideoFrameReceiver;
-};  // namespace media
+}  // namespace media
 
 namespace content {
 
diff --git a/content/browser/media/media_web_contents_observer.cc b/content/browser/media/media_web_contents_observer.cc
index 6e29f3c..740bdc6 100644
--- a/content/browser/media/media_web_contents_observer.cc
+++ b/content/browser/media/media_web_contents_observer.cc
@@ -77,11 +77,6 @@
     picture_in_picture_allowed_in_fullscreen_.reset();
     fullscreen_player_.reset();
   }
-
-  // Usually the frame will exit PIP before it is deleted but for OOPIF, it
-  // seems that the player never notifies the browser process.
-  if (pip_player_ && pip_player_->render_frame_host == render_frame_host)
-    ExitPictureInPictureInternal();
 }
 
 void MediaWebContentsObserver::MaybeUpdateAudibleState() {
@@ -120,15 +115,6 @@
   return fullscreen_player_;
 }
 
-const base::Optional<WebContentsObserver::MediaPlayerId>&
-MediaWebContentsObserver::GetPictureInPictureVideoMediaPlayerId() const {
-  return pip_player_;
-}
-
-void MediaWebContentsObserver::ResetPictureInPictureVideoMediaPlayerId() {
-  pip_player_.reset();
-}
-
 bool MediaWebContentsObserver::OnMessageReceived(
     const IPC::Message& msg,
     RenderFrameHost* render_frame_host) {
@@ -148,16 +134,8 @@
     IPC_MESSAGE_HANDLER(MediaPlayerDelegateHostMsg_OnMediaSizeChanged,
                         OnMediaSizeChanged)
     IPC_MESSAGE_HANDLER(
-        MediaPlayerDelegateHostMsg_OnPictureInPictureModeStarted,
-        OnPictureInPictureModeStarted)
-    IPC_MESSAGE_HANDLER(MediaPlayerDelegateHostMsg_OnPictureInPictureModeEnded,
-                        OnPictureInPictureModeEnded)
-    IPC_MESSAGE_HANDLER(
         MediaPlayerDelegateHostMsg_OnSetPictureInPictureCustomControls,
         OnSetPictureInPictureCustomControls)
-    IPC_MESSAGE_HANDLER(
-        MediaPlayerDelegateHostMsg_OnPictureInPictureSurfaceChanged,
-        OnPictureInPictureSurfaceChanged)
     IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
   return handled;
@@ -187,16 +165,6 @@
   return MediaPlayerEntryExists(player_id, active_audio_players_);
 }
 
-void MediaWebContentsObserver::OnPictureInPictureWindowResize(
-    const gfx::Size& window_size) {
-  DCHECK(pip_player_.has_value());
-
-  pip_player_->render_frame_host->Send(
-      new MediaPlayerDelegateMsg_OnPictureInPictureWindowResize(
-          pip_player_->render_frame_host->GetRoutingID(),
-          pip_player_->delegate_id, window_size));
-}
-
 void MediaWebContentsObserver::OnMediaDestroyed(
     RenderFrameHost* render_frame_host,
     int delegate_id) {
@@ -212,16 +180,6 @@
   const bool removed_video =
       RemoveMediaPlayerEntry(player_id, &active_video_players_);
 
-  if (!web_contents()->IsBeingDestroyed() && pip_player_ == player_id) {
-    PictureInPictureWindowControllerImpl* pip_controller =
-        PictureInPictureWindowControllerImpl::FromWebContents(
-            web_contents_impl());
-    if (pip_controller) {
-      pip_controller->UpdatePlaybackState(false /* is not playing */,
-                                          reached_end_of_stream);
-    }
-  }
-
   if (removed_audio || removed_video) {
     // Notify observers the player has been "paused".
     web_contents_impl()->MediaStoppedPlaying(
@@ -263,16 +221,6 @@
     return;
   }
 
-  if (!web_contents()->IsBeingDestroyed() && pip_player_ == id) {
-    PictureInPictureWindowControllerImpl* pip_controller =
-        PictureInPictureWindowControllerImpl::FromWebContents(
-            web_contents_impl());
-    if (pip_controller) {
-      pip_controller->UpdatePlaybackState(true /* is not playing */,
-                                          false /* reached_end_of_stream */);
-    }
-  }
-
   // Notify observers of the new player.
   web_contents_impl()->MediaStartedPlaying(
       WebContentsObserver::MediaPlayerInfo(has_video, has_audio), id);
@@ -317,41 +265,6 @@
   web_contents_impl()->MediaResized(size, id);
 }
 
-void MediaWebContentsObserver::OnPictureInPictureModeStarted(
-    RenderFrameHost* render_frame_host,
-    int delegate_id,
-    const viz::SurfaceId& surface_id,
-    const gfx::Size& natural_size,
-    int request_id,
-    bool show_play_pause_button) {
-  DCHECK(surface_id.is_valid());
-  pip_player_ = MediaPlayerId(render_frame_host, delegate_id);
-
-  gfx::Size window_size =
-      web_contents_impl()->EnterPictureInPicture(surface_id, natural_size);
-
-  if (auto* pip_controller =
-          PictureInPictureWindowControllerImpl::FromWebContents(
-              web_contents_impl()))
-    pip_controller->SetAlwaysHidePlayPauseButton(show_play_pause_button);
-
-  render_frame_host->Send(
-      new MediaPlayerDelegateMsg_OnPictureInPictureModeStarted_ACK(
-          render_frame_host->GetRoutingID(), delegate_id, request_id,
-          window_size));
-}
-
-void MediaWebContentsObserver::OnPictureInPictureModeEnded(
-    RenderFrameHost* render_frame_host,
-    int delegate_id,
-    int request_id) {
-  ExitPictureInPictureInternal();
-
-  render_frame_host->Send(
-      new MediaPlayerDelegateMsg_OnPictureInPictureModeEnded_ACK(
-          render_frame_host->GetRoutingID(), delegate_id, request_id));
-}
-
 void MediaWebContentsObserver::OnSetPictureInPictureCustomControls(
     RenderFrameHost* render_frame_host,
     int delegate_id,
@@ -363,26 +276,6 @@
     pip_controller->SetPictureInPictureCustomControls(controls);
 }
 
-void MediaWebContentsObserver::OnPictureInPictureSurfaceChanged(
-    RenderFrameHost* render_frame_host,
-    int delegate_id,
-    const viz::SurfaceId& surface_id,
-    const gfx::Size& natural_size,
-    bool show_play_pause_button) {
-  DCHECK(surface_id.is_valid());
-
-  pip_player_ = MediaPlayerId(render_frame_host, delegate_id);
-
-  // The PictureInPictureWindowController instance may not have been created by
-  // the embedder.
-  if (auto* pip_controller =
-          PictureInPictureWindowControllerImpl::FromWebContents(
-              web_contents_impl())) {
-    pip_controller->EmbedSurface(surface_id, natural_size);
-    pip_controller->SetAlwaysHidePlayPauseButton(show_play_pause_button);
-  }
-}
-
 void MediaWebContentsObserver::ClearWakeLocks(
     RenderFrameHost* render_frame_host) {
   std::set<MediaPlayerId> video_players;
@@ -482,16 +375,6 @@
   player_map->erase(it);
 }
 
-void MediaWebContentsObserver::ExitPictureInPictureInternal() {
-  DCHECK(pip_player_);
-
-  web_contents_impl()->ExitPictureInPicture();
-
-  // Reset must happen after notifying the WebContents because it may interact
-  // with it.
-  ResetPictureInPictureVideoMediaPlayerId();
-}
-
 WebContentsImpl* MediaWebContentsObserver::web_contents_impl() const {
   return static_cast<WebContentsImpl*>(web_contents());
 }
diff --git a/content/browser/media/media_web_contents_observer.h b/content/browser/media/media_web_contents_observer.h
index d1eb2af..788c018 100644
--- a/content/browser/media/media_web_contents_observer.h
+++ b/content/browser/media/media_web_contents_observer.h
@@ -34,10 +34,6 @@
 class Size;
 }  // namespace size
 
-namespace viz {
-class SurfaceId;
-}  // namespace viz
-
 namespace content {
 
 class AudibleMetrics;
@@ -72,14 +68,6 @@
   // Gets the MediaPlayerId of the fullscreen video if it exists.
   const base::Optional<MediaPlayerId>& GetFullscreenVideoMediaPlayerId() const;
 
-  // Gets the MediaPlayerId of the picture in picture video if it exists.
-  const base::Optional<MediaPlayerId>& GetPictureInPictureVideoMediaPlayerId()
-      const;
-
-  // Reset the MediaPlayerId of the picture in picture video when user closes
-  // Picture-in-Picture window manually.
-  void ResetPictureInPictureVideoMediaPlayerId();
-
   // WebContentsObserver implementation.
   void WebContentsDestroyed() override;
   void RenderFrameDeleted(RenderFrameHost* render_frame_host) override;
@@ -96,11 +84,6 @@
   // Returns whether or not the given player id is active.
   bool IsPlayerActive(const MediaPlayerId& player_id) const;
 
-  // Called by the Picture-in-Picture controller when the associated window is
-  // resized. |window_size| represents the new size of the window. It MUST be
-  // called when there is a player in Picture-in-Picture.
-  void OnPictureInPictureWindowResize(const gfx::Size& window_size);
-
   bool has_audio_wake_lock_for_testing() const {
     return has_audio_wake_lock_for_testing_;
   }
@@ -135,24 +118,10 @@
   void OnMediaMutedStatusChanged(RenderFrameHost* render_frame_host,
                                  int delegate_id,
                                  bool muted);
-  void OnPictureInPictureModeStarted(RenderFrameHost* render_frame_host,
-                                     int delegate_id,
-                                     const viz::SurfaceId&,
-                                     const gfx::Size& natural_size,
-                                     int request_id,
-                                     bool show_play_pause_button);
-  void OnPictureInPictureModeEnded(RenderFrameHost* render_frame_host,
-                                   int delegate_id,
-                                   int request_id);
   void OnSetPictureInPictureCustomControls(
       RenderFrameHost* render_frame_host,
       int delegate_id,
       const std::vector<blink::PictureInPictureControlInfo>& controls);
-  void OnPictureInPictureSurfaceChanged(RenderFrameHost*,
-                                        int delegate_id,
-                                        const viz::SurfaceId&,
-                                        const gfx::Size&,
-                                        bool show_play_pause_button);
 
   // Clear |render_frame_host|'s tracking entry for its WakeLocks.
   void ClearWakeLocks(RenderFrameHost* render_frame_host);
@@ -176,10 +145,6 @@
                                    ActiveMediaPlayerMap* player_map,
                                    std::set<MediaPlayerId>* removed_players);
 
-  // Internal method to exit Picture-in-Picture from an event received from the
-  // renderer process.
-  void ExitPictureInPictureInternal();
-
   // Convenience method that casts web_contents() to a WebContentsImpl*.
   WebContentsImpl* web_contents_impl() const;
 
@@ -191,7 +156,6 @@
   ActiveMediaPlayerMap active_video_players_;
   device::mojom::WakeLockPtr audio_wake_lock_;
   base::Optional<MediaPlayerId> fullscreen_player_;
-  base::Optional<MediaPlayerId> pip_player_;
   base::Optional<bool> picture_in_picture_allowed_in_fullscreen_;
   bool has_audio_wake_lock_for_testing_ = false;
 
diff --git a/content/browser/picture_in_picture/picture_in_picture_service_impl.cc b/content/browser/picture_in_picture/picture_in_picture_service_impl.cc
new file mode 100644
index 0000000..4d3bc41
--- /dev/null
+++ b/content/browser/picture_in_picture/picture_in_picture_service_impl.cc
@@ -0,0 +1,130 @@
+// Copyright 2019 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 "content/browser/picture_in_picture/picture_in_picture_service_impl.h"
+
+#include <utility>
+
+#include "content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+
+namespace content {
+
+// static
+void PictureInPictureServiceImpl::Create(
+    RenderFrameHost* render_frame_host,
+    blink::mojom::PictureInPictureServiceRequest request) {
+  DCHECK(render_frame_host);
+  new PictureInPictureServiceImpl(render_frame_host, std::move(request));
+}
+
+// static
+PictureInPictureServiceImpl* PictureInPictureServiceImpl::CreateForTesting(
+    RenderFrameHost* render_frame_host,
+    blink::mojom::PictureInPictureServiceRequest request) {
+  return new PictureInPictureServiceImpl(render_frame_host, std::move(request));
+}
+
+void PictureInPictureServiceImpl::StartSession(
+    uint32_t player_id,
+    const base::Optional<viz::SurfaceId>& surface_id,
+    const gfx::Size& natural_size,
+    bool show_play_pause_button,
+    StartSessionCallback callback) {
+  player_id_ = MediaPlayerId(render_frame_host_, player_id);
+
+  auto* pip_controller = GetController();
+  if (pip_controller)
+    pip_controller->set_service(this);
+
+  gfx::Size window_size = web_contents_impl()->EnterPictureInPicture(
+      surface_id.value(), natural_size);
+
+  if (pip_controller)
+    pip_controller->SetAlwaysHidePlayPauseButton(show_play_pause_button);
+
+  std::move(callback).Run(window_size);
+}
+
+void PictureInPictureServiceImpl::EndSession(EndSessionCallback callback) {
+  DCHECK(player_id_);
+
+  ExitPictureInPictureInternal();
+
+  std::move(callback).Run();
+}
+
+void PictureInPictureServiceImpl::UpdateSession(
+    uint32_t player_id,
+    const base::Optional<viz::SurfaceId>& surface_id,
+    const gfx::Size& natural_size,
+    bool show_play_pause_button) {
+  player_id_ = MediaPlayerId(render_frame_host_, player_id);
+
+  // The PictureInPictureWindowController instance may not have been created by
+  // the embedder.
+  if (auto* pip_controller = GetController()) {
+    pip_controller->EmbedSurface(surface_id.value(), natural_size);
+    pip_controller->SetAlwaysHidePlayPauseButton(show_play_pause_button);
+    pip_controller->set_service(this);
+  }
+}
+
+void PictureInPictureServiceImpl::SetDelegate(
+    blink::mojom::PictureInPictureDelegatePtr delegate) {
+  delegate.set_connection_error_handler(
+      base::BindOnce(&PictureInPictureServiceImpl::OnDelegateDisconnected,
+                     // delegate is held by |this|.
+                     base::Unretained(this)));
+
+  if (delegate_)
+    mojo::ReportBadMessage("SetDelegate() should only be called once.");
+
+  delegate_ = std::move(delegate);
+}
+
+void PictureInPictureServiceImpl::NotifyWindowResized(const gfx::Size& size) {
+  if (delegate_)
+    delegate_->PictureInPictureWindowSizeChanged(size);
+}
+
+PictureInPictureServiceImpl::PictureInPictureServiceImpl(
+    RenderFrameHost* render_frame_host,
+    blink::mojom::PictureInPictureServiceRequest request)
+    : FrameServiceBase(render_frame_host, std::move(request)),
+      render_frame_host_(render_frame_host) {}
+
+PictureInPictureServiceImpl::~PictureInPictureServiceImpl() {
+  if (player_id_)
+    ExitPictureInPictureInternal();
+  if (GetController())
+    GetController()->set_service(nullptr);
+}
+
+PictureInPictureWindowControllerImpl*
+PictureInPictureServiceImpl::GetController() {
+  return PictureInPictureWindowControllerImpl::GetOrCreateForWebContents(
+      web_contents_impl());
+}
+
+void PictureInPictureServiceImpl::OnDelegateDisconnected() {
+  delegate_ = nullptr;
+}
+
+void PictureInPictureServiceImpl::ExitPictureInPictureInternal() {
+  web_contents_impl()->ExitPictureInPicture();
+
+  // Reset must happen after notifying the WebContents because it may interact
+  // with it.
+  player_id_.reset();
+
+  if (auto* controller = GetController())
+    controller->set_service(nullptr);
+}
+
+WebContentsImpl* PictureInPictureServiceImpl::web_contents_impl() {
+  return static_cast<WebContentsImpl*>(web_contents());
+}
+
+}  // namespace content
diff --git a/content/browser/picture_in_picture/picture_in_picture_service_impl.h b/content/browser/picture_in_picture/picture_in_picture_service_impl.h
new file mode 100644
index 0000000..28710e3
--- /dev/null
+++ b/content/browser/picture_in_picture/picture_in_picture_service_impl.h
@@ -0,0 +1,78 @@
+// Copyright 2019 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 CONTENT_BROWSER_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_SERVICE_IMPL_H_
+#define CONTENT_BROWSER_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_SERVICE_IMPL_H_
+
+#include "base/containers/unique_ptr_adapters.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/frame_service_base.h"
+#include "third_party/blink/public/mojom/picture_in_picture/picture_in_picture.mojom.h"
+
+namespace content {
+
+class PictureInPictureWindowControllerImpl;
+
+// Receives Picture-in-Picture messages from a given RenderFrame. There is one
+// PictureInPictureServiceImpl per RenderFrameHost. It talks directly to the
+// PictureInPictureWindowControllerImpl. Only one service interacts with the
+// window at a given time.
+class CONTENT_EXPORT PictureInPictureServiceImpl final
+    : public content::FrameServiceBase<blink::mojom::PictureInPictureService> {
+ public:
+  static void Create(RenderFrameHost*,
+                     blink::mojom::PictureInPictureServiceRequest);
+  static PictureInPictureServiceImpl* CreateForTesting(
+      RenderFrameHost*,
+      blink::mojom::PictureInPictureServiceRequest);
+
+  // PictureInPictureService implementation.
+  void StartSession(uint32_t player_id,
+                    const base::Optional<viz::SurfaceId>& surface_id,
+                    const gfx::Size& natural_size,
+                    bool show_play_pause_button,
+                    StartSessionCallback) final;
+  void EndSession(EndSessionCallback) final;
+  void UpdateSession(uint32_t player_id,
+                     const base::Optional<viz::SurfaceId>& surface_id,
+                     const gfx::Size& natural_size,
+                     bool show_play_pause_button) final;
+  void SetDelegate(blink::mojom::PictureInPictureDelegatePtr) final;
+
+  void NotifyWindowResized(const gfx::Size&);
+
+  // Returns the player that is currently in Picture-in-Picture in the context
+  // of the frame associated with the service. Returns nullopt if there are
+  // none.
+  const base::Optional<MediaPlayerId>& player_id() const { return player_id_; }
+  void ResetPlayerId() { player_id_.reset(); }
+
+ private:
+  PictureInPictureServiceImpl(RenderFrameHost*,
+                              blink::mojom::PictureInPictureServiceRequest);
+  ~PictureInPictureServiceImpl() override;
+
+  // Returns the PictureInPictureWindowControllerImpl associated with the
+  // WebContents. Can be null.
+  PictureInPictureWindowControllerImpl* GetController();
+
+  // Callack run when the delegate is disconnected. Only one delegate should be
+  // set at any given time.
+  void OnDelegateDisconnected();
+
+  // Implementation of ExitPictureInPicture without callback handling.
+  void ExitPictureInPictureInternal();
+
+  WebContentsImpl* web_contents_impl();
+
+  blink::mojom::PictureInPictureDelegatePtr delegate_ = nullptr;
+  RenderFrameHost* render_frame_host_ = nullptr;
+  base::Optional<MediaPlayerId> player_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(PictureInPictureServiceImpl);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_SERVICE_IMPL_H_
diff --git a/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc b/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
new file mode 100644
index 0000000..abefe2a56
--- /dev/null
+++ b/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
@@ -0,0 +1,145 @@
+// Copyright 2019 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 "content/browser/picture_in_picture/picture_in_picture_service_impl.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "content/common/media/media_player_delegate_messages.h"
+#include "content/public/browser/overlay_window.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/test/test_content_browser_client.h"
+#include "content/test/test_render_frame_host.h"
+#include "content/test/test_render_view_host.h"
+#include "content/test/test_web_contents.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace content {
+
+class PictureInPictureDelegate : public WebContentsDelegate {
+ public:
+  PictureInPictureDelegate() = default;
+
+  MOCK_METHOD3(EnterPictureInPicture,
+               gfx::Size(WebContents*,
+                         const viz::SurfaceId&,
+                         const gfx::Size&));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PictureInPictureDelegate);
+};
+
+class TestOverlayWindow : public OverlayWindow {
+ public:
+  TestOverlayWindow() = default;
+  ~TestOverlayWindow() override{};
+
+  static std::unique_ptr<OverlayWindow> Create(
+      PictureInPictureWindowController* controller) {
+    return std::unique_ptr<OverlayWindow>(new TestOverlayWindow());
+  }
+
+  bool IsActive() const override { return false; }
+  void Close() override {}
+  void ShowInactive() override {}
+  void Hide() override {}
+  void SetPictureInPictureCustomControls(
+      const std::vector<blink::PictureInPictureControlInfo>& controls)
+      override {}
+  bool IsVisible() const override { return false; }
+  bool IsAlwaysOnTop() const override { return false; }
+  ui::Layer* GetLayer() override { return nullptr; }
+  gfx::Rect GetBounds() const override { return gfx::Rect(); }
+  void UpdateVideoSize(const gfx::Size& natural_size) override {}
+  void SetPlaybackState(PlaybackState playback_state) override {}
+  void SetAlwaysHidePlayPauseButton(bool is_visible) override {}
+  ui::Layer* GetWindowBackgroundLayer() override { return nullptr; }
+  ui::Layer* GetVideoLayer() override { return nullptr; }
+  gfx::Rect GetVideoBounds() override { return gfx::Rect(); }
+  void SetSkipAdButtonVisibility(bool is_visible) override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestOverlayWindow);
+};
+
+class PictureInPictureTestBrowserClient : public TestContentBrowserClient {
+ public:
+  PictureInPictureTestBrowserClient() = default;
+  ~PictureInPictureTestBrowserClient() override = default;
+
+  std::unique_ptr<OverlayWindow> CreateWindowForPictureInPicture(
+      PictureInPictureWindowController* controller) override {
+    return TestOverlayWindow::Create(controller);
+  }
+};
+
+class PictureInPictureServiceImplTest : public RenderViewHostImplTestHarness {
+ public:
+  void SetUp() override {
+    RenderViewHostImplTestHarness::SetUp();
+    // WebUIControllerFactory::RegisterFactory(
+    //     ContentWebUIControllerFactory::GetInstance());
+
+    SetBrowserClientForTesting(&browser_client_);
+
+    TestRenderFrameHost* render_frame_host = contents()->GetMainFrame();
+    render_frame_host->InitializeRenderFrameIfNeeded();
+
+    contents()->SetDelegate(&delegate_);
+
+    blink::mojom::PictureInPictureServiceRequest request;
+    service_impl_ = PictureInPictureServiceImpl::CreateForTesting(
+        render_frame_host, std::move(request));
+  }
+
+  void TearDown() override {
+    // WebUIControllerFactory::UnregisterFactoryForTesting(
+    //     ContentWebUIControllerFactory::GetInstance());
+    RenderViewHostImplTestHarness::TearDown();
+  }
+
+  PictureInPictureServiceImpl& service() { return *service_impl_; }
+
+  PictureInPictureDelegate& delegate() { return delegate_; }
+
+ private:
+  PictureInPictureTestBrowserClient browser_client_;
+  PictureInPictureDelegate delegate_;
+  // Will be deleted when the frame is destroyed.
+  PictureInPictureServiceImpl* service_impl_;
+};
+
+TEST_F(PictureInPictureServiceImplTest, EnterPictureInPicture) {
+  const int kPlayerVideoOnlyId = 30;
+
+  // If Picture-in-Picture was never triggered, the media player id would not be
+  // set.
+  EXPECT_FALSE(service().player_id().has_value());
+
+  viz::SurfaceId surface_id =
+      viz::SurfaceId(viz::FrameSinkId(1, 1),
+                     viz::LocalSurfaceId(
+                         11, base::UnguessableToken::Deserialize(0x111111, 0)));
+
+  EXPECT_CALL(delegate(),
+              EnterPictureInPicture(contents(), surface_id, gfx::Size(42, 42)));
+
+  service().StartSession(kPlayerVideoOnlyId, surface_id, gfx::Size(42, 42),
+                         true /* show_play_pause_button */, base::DoNothing());
+  EXPECT_TRUE(service().player_id().has_value());
+  EXPECT_EQ(kPlayerVideoOnlyId, service().player_id()->delegate_id);
+
+  // Picture-in-Picture media player id should not be reset when the media is
+  // destroyed (e.g. video stops playing). This allows the Picture-in-Picture
+  // window to continue to control the media.
+  contents()->GetMainFrame()->OnMessageReceived(
+      MediaPlayerDelegateHostMsg_OnMediaDestroyed(
+          contents()->GetMainFrame()->GetRoutingID(), kPlayerVideoOnlyId));
+  EXPECT_TRUE(service().player_id().has_value());
+}
+
+}  // namespace content
diff --git a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
index 44534b1..b26f9d0 100644
--- a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
+++ b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
@@ -10,6 +10,7 @@
 #include "content/browser/media/media_web_contents_observer.h"
 #include "content/browser/media/session/media_session_impl.h"
 #include "content/browser/picture_in_picture/overlay_surface_embedder.h"
+#include "content/browser/picture_in_picture/picture_in_picture_service_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/common/media/media_player_delegate_messages.h"
 #include "content/public/browser/content_browser_client.h"
@@ -57,7 +58,8 @@
 
 PictureInPictureWindowControllerImpl::PictureInPictureWindowControllerImpl(
     WebContents* initiator)
-    : initiator_(static_cast<WebContentsImpl* const>(initiator)) {
+    : WebContentsObserver(initiator),
+      initiator_(static_cast<WebContentsImpl* const>(initiator)) {
   DCHECK(initiator_);
 
   media_web_contents_observer_ = initiator_->media_web_contents_observer();
@@ -126,9 +128,7 @@
   // surface id was updated for the same video, this is a no-op. This could
   // be updated for a different video if another media player on the same
   // |initiator_| enters Picture-in-Picture mode.
-  media_player_id_ =
-      media_web_contents_observer_->GetPictureInPictureVideoMediaPlayerId();
-  UpdatePlaybackState(IsPlayerActive(), !media_player_id_.has_value());
+  UpdateMediaPlayerId();
 
   window_->UpdateVideoSize(natural_size);
 
@@ -142,9 +142,9 @@
 }
 
 void PictureInPictureWindowControllerImpl::UpdateLayerBounds() {
-  if (media_player_id_.has_value() && window_ && window_->IsVisible()) {
-    media_web_contents_observer_->OnPictureInPictureWindowResize(
-        window_->GetBounds().size());
+  if (media_player_id_.has_value() && service_ && window_ &&
+      window_->IsVisible()) {
+    service_->NotifyWindowResized(window_->GetBounds().size());
   }
 
   if (embedder_)
@@ -152,10 +152,12 @@
 }
 
 bool PictureInPictureWindowControllerImpl::IsPlayerActive() {
-  if (!media_player_id_.has_value()) {
-    media_player_id_ =
-        media_web_contents_observer_->GetPictureInPictureVideoMediaPlayerId();
-  }
+  if (!media_player_id_.has_value())
+    media_player_id_ = service_ ? service_->player_id() : base::nullopt;
+
+  // At creation time, the player id may not be set.
+  if (!media_player_id_.has_value())
+    return false;
 
   return media_player_id_.has_value() &&
          media_web_contents_observer_->IsPlayerActive(*media_player_id_);
@@ -219,6 +221,11 @@
           media_player_id_->delegate_id, control_id));
 }
 
+void PictureInPictureWindowControllerImpl::UpdateMediaPlayerId() {
+  media_player_id_ = service_ ? service_->player_id() : base::nullopt;
+  UpdatePlaybackState(IsPlayerActive(), !media_player_id_.has_value());
+}
+
 void PictureInPictureWindowControllerImpl::SetAlwaysHidePlayPauseButton(
     bool is_visible) {
   always_hide_play_pause_button_ = is_visible;
@@ -254,6 +261,33 @@
   window_->SetSkipAdButtonVisibility(media_session_action_skip_ad_handled_);
 }
 
+void PictureInPictureWindowControllerImpl::MediaStartedPlaying(
+    const MediaPlayerInfo&,
+    const MediaPlayerId& media_player_id) {
+  if (initiator_->IsBeingDestroyed())
+    return;
+
+  if (media_player_id_ != media_player_id)
+    return;
+
+  UpdatePlaybackState(true /* is_playing */, false /* reached_end_of_stream */);
+}
+
+void PictureInPictureWindowControllerImpl::MediaStoppedPlaying(
+    const MediaPlayerInfo&,
+    const MediaPlayerId& media_player_id,
+    WebContentsObserver::MediaStoppedReason reason) {
+  if (initiator_->IsBeingDestroyed())
+    return;
+
+  if (media_player_id_ != media_player_id)
+    return;
+
+  UpdatePlaybackState(
+      false /* is_playing */,
+      reason == WebContentsObserver::MediaStoppedReason::kReachedEndOfStream);
+}
+
 void PictureInPictureWindowControllerImpl::OnLeavingPictureInPicture(
     bool should_pause_video,
     bool should_reset_pip_player) {
@@ -269,8 +303,12 @@
         new MediaPlayerDelegateMsg_EndPictureInPictureMode(
             media_player_id_->render_frame_host->GetRoutingID(),
             media_player_id_->delegate_id));
-    if (should_reset_pip_player)
-      media_web_contents_observer_->ResetPictureInPictureVideoMediaPlayerId();
+
+    if (should_reset_pip_player) {
+      DCHECK(service_);
+      service_->ResetPlayerId();
+      media_player_id_.reset();
+    }
   }
 }
 
diff --git a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h
index f44a7ca..91701f7 100644
--- a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h
+++ b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h
@@ -14,10 +14,11 @@
 
 namespace content {
 
+class MediaWebContentsObserver;
 class OverlaySurfaceEmbedder;
+class PictureInPictureServiceImpl;
 class WebContents;
 class WebContentsImpl;
-class MediaWebContentsObserver;
 
 // TODO(thakis,mlamouri): PictureInPictureWindowControllerImpl isn't
 // CONTENT_EXPORT'd because it creates complicated build issues with
@@ -27,7 +28,8 @@
 // work with it. https://crbug.com/589840.
 class PictureInPictureWindowControllerImpl
     : public PictureInPictureWindowController,
-      public WebContentsUserData<PictureInPictureWindowControllerImpl> {
+      public WebContentsUserData<PictureInPictureWindowControllerImpl>,
+      public WebContentsObserver {
  public:
   // Gets a reference to the controller associated with |initiator| and creates
   // one if it does not exist. The returned pointer is guaranteed to be
@@ -62,6 +64,22 @@
   CONTENT_EXPORT void MediaSessionActionsChanged(
       const std::set<media_session::mojom::MediaSessionAction>& actions);
 
+  // WebContentsObserver:
+  void MediaStartedPlaying(const MediaPlayerInfo&,
+                           const MediaPlayerId&) override;
+  void MediaStoppedPlaying(const MediaPlayerInfo&,
+                           const MediaPlayerId&,
+                           WebContentsObserver::MediaStoppedReason) override;
+
+  // TODO(mlamouri): temporary method used because of the media player id is
+  // stored in a different location from the one that is used to update the
+  // state of this object.
+  void UpdateMediaPlayerId();
+
+  void set_service(PictureInPictureServiceImpl* service) {
+    service_ = service;
+  };
+
  private:
   friend class WebContentsUserData<PictureInPictureWindowControllerImpl>;
 
@@ -89,6 +107,7 @@
 
   std::unique_ptr<OverlayWindow> window_;
   std::unique_ptr<OverlaySurfaceEmbedder> embedder_;
+  // TODO(929156): remove this as it should be accessible via `web_contents()`.
   WebContentsImpl* const initiator_;
 
   // Used to determine the state of the media player and route messages to
@@ -109,6 +128,11 @@
   // Session API in UpdatePlayPauseButtonVisibility().
   bool always_hide_play_pause_button_ = false;
 
+  // Service currently associated with the Picture-in-Picture window. The
+  // service makes the bridge with the renderer process by sending enter/exit
+  // requests. It is also holding the Picture-in-Picture MediaPlayerId.
+  PictureInPictureServiceImpl* service_ = nullptr;
+
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 
   DISALLOW_COPY_AND_ASSIGN(PictureInPictureWindowControllerImpl);
diff --git a/content/browser/presentation/presentation_service_impl.cc b/content/browser/presentation/presentation_service_impl.cc
index 5728452..d9dc94b 100644
--- a/content/browser/presentation/presentation_service_impl.cc
+++ b/content/browser/presentation/presentation_service_impl.cc
@@ -209,13 +209,6 @@
     const std::vector<GURL>& presentation_urls,
     NewPresentationCallback callback) {
   DVLOG(2) << "StartPresentation";
-  if (!controller_delegate_) {
-    std::move(callback).Run(
-        /** PresentationConnectionResultPtr */ nullptr,
-        PresentationError::N