diff --git a/AUTHORS b/AUTHORS
index 759394b..8b7f7b1 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -332,6 +332,7 @@
 Hautio Kari <khautio@gmail.com>
 Heejin R. Chung <heejin.r.chung@samsung.com>
 Heeyoun Lee <heeyoun.lee@samsung.com>
+Henrique Limas <henrique.ramos.limas@gmail.com>
 Himanshu Joshi <h.joshi@samsung.com>
 Holger Kraus <kraush@amazon.com>
 Hong Zheng <hong.zheng@intel.com>
@@ -422,7 +423,6 @@
 Jin Yang <jin.a.yang@intel.com>
 Jincheol Jo <jincheol.jo@navercorp.com>
 Jing Zhao <zhaojing7@xiaomi.com>
-Jinglong Zuo <zuojinglong@xiaomi.com>
 Jingwei Liu <kingweiliu@gmail.com>
 Jingyi Wei <wjywbs@gmail.com>
 Jinho Bang <jinho.bang@samsung.com>
diff --git a/DEPS b/DEPS
index fba6e8e..933c504 100644
--- a/DEPS
+++ b/DEPS
@@ -138,34 +138,34 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'e63b01b364a0e86668ff4c4508b725b2e6752c00',
+  'skia_revision': 'c51d791ab99228a9163c3313bb61015fb096f03f',
   # 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': '6ba8b68b85689114be67b049e98820cad056c75d',
+  'v8_revision': '5b40be11f04192c14b6b15fc273012b25e64725c',
   # 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.
-  'swarming_revision': '1b65f4e862045f3ba430a4cbd0643f5b1b66c230',
+  'swarming_revision': '779c4f0f8488c64587b75dbb001d18c3c0c4cda9',
   # 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': 'fb8e1b25ad7165591b9df6a1190316610bd6476b',
+  'angle_revision': '344ecaa6755dac1da0c1843fc0b69d5ff8207f8d',
   # 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': 'f4d2a446cc125cc147c7fc5f1a4bcbc8845b1a24',
+  'swiftshader_revision': '8458639991d4cf866ea5445550c9b24f84c11146',
   # 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': '176c657a6d073a53cdafa17c64f626da482424ea',
+  'pdfium_revision': 'a1741dfd1f88471fb5ccc93f8194d778e027ef55',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
   #
   # Note this revision should be updated with
   # third_party/boringssl/roll_boringssl.py, not roll-dep.
-  'boringssl_revision': 'f014d609c07cf83d5a9be730469f67bd199c017e',
+  'boringssl_revision': '35a5a9e7beddb9cec70d7bc1b70a6ac441093f87',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -201,7 +201,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': '535dc1d8e2b7372951dfeb8e57f592bf0c823870',
+  'catapult_revision': '1d6ef8a04840e9e9f8a09b3f4ad591229b769793',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -257,7 +257,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': '9dfd4b8358077bdbe8e2f9388572b5376c370f5d',
+  'spv_tools_revision': '07a10197179143aee95a016f9a24edc8dd1d5b64',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -273,7 +273,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': 'c0c7e2f85bce063edf595ac014c040f0c7d04280',
+  'dawn_revision': '1c85976abe4e059684375915f955fc36049c8380',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -297,7 +297,7 @@
   # Also, if you change these, update buildtools/DEPS too. Also update the
   # libc++ svn_revision in //buildtools/deps_revisions.gni.
   'clang_format_revision': '96636aa0e9f047f17447f2d45a094d0b59ed7917',
-  'libcxx_revision': '9b96c3dbd4e89c10d9fd8364da4b65f93c6f4276',
+  'libcxx_revision': '5938e0582bac570a41edb3d6a2217c299adc1bc6',
   'libcxxabi_revision': '0d529660e32d77d9111912d73f2c74fc5fa2a858',
   'libunwind_revision': '69d9b84cca8354117b9fe9705a4430d789ee599b',
 }
@@ -477,7 +477,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'e98ef9c2c9be7e48af15273d4076e730b68fe28e',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '2a20a6899d0b8c47de26d6a33a12ece09821718a',
       'condition': 'checkout_ios',
   },
 
@@ -807,7 +807,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'b5e56574cb70bb41c5f9a38062ac59dc9f5b1cfa',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '7cb7b0fac7f44f7614747fca7344c18f13431926',
       'condition': 'checkout_linux',
   },
 
@@ -832,7 +832,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'c7e440c0091d166e956476b21b51b630ff750d1b',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '181e44c231854a3f201ef0884f6f8462f321ea15',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1076,7 +1076,7 @@
   },
 
   'src/third_party/libvpx/source/libvpx':
-    Var('chromium_git') + '/webm/libvpx.git' + '@' +  '78c44e2dc26e383fdc54eb4bf406a76e52ea361e',
+    Var('chromium_git') + '/webm/libvpx.git' + '@' +  '197827edb87eb9387d946af999366c4ec5f0d88e',
 
   'src/third_party/libwebm/source':
     Var('chromium_git') + '/webm/libwebm.git' + '@' + '51ca718c3adf0ddedacd7df25fe45f67dc5a9ce1',
@@ -1350,7 +1350,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '6f0b34abee8dba611c253738d955c59f703c147a',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '4f08faae82ccb0a252e83aec2baa9598c9f1716a',
+    Var('webrtc_git') + '/src.git' + '@' + '58c71db1b344295a059ae72d9fe247b7c1cd1047',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1391,7 +1391,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@8908a37777ec27312e227ec935c80eddd2100af2',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@cbc5d3765f6061c9e3bb9bca73b681e2c7f50f98',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index 512e4ac..3721856 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1539,6 +1539,9 @@
                   '|chrome/browser/resources/settings/os_settings_resouces.grd'\
                   '|chrome/browser/resources/settings/os_settings_resouces_vulcanized.grd',
     },
+    'settings_os_settings': {
+      'filepath': 'chrome/browser/resources/settings/chromeos/',
+    },
     'settings_reset_prompt': {
       'filepath': 'chrome/browser/safe_browsing/settings_reset_prompt/'\
                   '|chrome/browser/ui/views/settings_reset_prompt',
@@ -2522,11 +2525,12 @@
                  'michaelpg+watch-md-settings@chromium.org',
                  'stevenjb+watch-md-settings@chromium.org',
                  'hsuregan+watch@chromium.org',
-                 'jhawkins+watch@chromium.org',
                  'jordynass+watch@chromium.org',
                  'maybelle+watch@chromium.org'],
     'settings_forked_os_settings': [
                  'maybelle@chromium.org'],
+    'settings_os_settings': [
+                 'jhawkins+watch@chromium.org'],
     'settings_reset_prompt': ['alito+watch@chromium.org'],
     'site_engagement': ['dominickn+watch-engagement@chromium.org'],
     'site_instance': ['ajwong+watch@chromium.org',
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index 5d2b36a..c0f3bdb 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -1098,6 +1098,22 @@
   android_manifest_for_lint = system_webview_android_manifest
 }
 
+# Generate LocaleConfig.java for android_webview_locale_config_java's
+# compile step works.
+generate_locale_config_srcjar("webview_locale_config") {
+  java_package = "org.chromium.android_webview"
+}
+
+# LocaleConfig.java is excluded from the generated .jar
+# (via. jar_excluded_patterns) and the final version is inserted at the APK
+# level - with the list of pak locales populated by looking at the assets that
+# are listed in the final APK's .build_config.
+android_library("android_webview_locale_config_java") {
+  java_files = [ "java/src/org/chromium/android_webview/AwLocaleConfig.java" ]
+  srcjar_deps = [ ":webview_locale_config" ]
+  jar_excluded_patterns = [ "*/LocaleConfig.class" ]
+}
+
 android_aidl("crash_receiver_aidl") {
   import_include = [ "java/src" ]
   sources = [
diff --git a/android_webview/apk/BUILD.gn b/android_webview/apk/BUILD.gn
index 8f5d555..47e9226 100644
--- a/android_webview/apk/BUILD.gn
+++ b/android_webview/apk/BUILD.gn
@@ -31,7 +31,9 @@
     "java/src/com/android/webview/chromium/WebViewApkApplication.java",
   ]
   deps = [
+    "//android_webview:android_webview_locale_config_java",
     "//base:base_java",
     "//components/embedder_support/android:application_java",
+    "//ui/android:ui_java",
   ]
 }
diff --git a/android_webview/apk/java/src/com/android/webview/chromium/WebViewApkApplication.java b/android_webview/apk/java/src/com/android/webview/chromium/WebViewApkApplication.java
index 303ee89..22d7b54 100644
--- a/android_webview/apk/java/src/com/android/webview/chromium/WebViewApkApplication.java
+++ b/android_webview/apk/java/src/com/android/webview/chromium/WebViewApkApplication.java
@@ -7,10 +7,12 @@
 import android.app.Application;
 import android.content.Context;
 
+import org.chromium.android_webview.AwLocaleConfig;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.components.embedder_support.application.FontPreloadingWorkaround;
+import org.chromium.ui.base.ResourceBundle;
 
 /**
  * Application subclass for SystemWebView and Trichrome.
@@ -50,6 +52,8 @@
             return false;
         }
         LibraryLoader.getInstance().switchCommandLineForWebView();
+        ResourceBundle.setAvailablePakLocales(
+                new String[] {}, AwLocaleConfig.getWebViewSupportedPakLocales());
         nativeInitializePakResources();
         return true;
     }
diff --git a/android_webview/browser/aw_download_manager_delegate.cc b/android_webview/browser/aw_download_manager_delegate.cc
index 891d00d..63c263a0 100644
--- a/android_webview/browser/aw_download_manager_delegate.cc
+++ b/android_webview/browser/aw_download_manager_delegate.cc
@@ -71,6 +71,9 @@
     const std::string& request_origin,
     int64_t content_length,
     content::WebContents* web_contents) {
+  if (!web_contents)
+    return false;
+
   std::string aw_user_agent = web_contents->GetUserAgentOverride();
   if (aw_user_agent.empty()) {
     // use default user agent if nothing is provided
diff --git a/android_webview/glue/glue.gni b/android_webview/glue/glue.gni
index 0581c9b..aba86bd 100644
--- a/android_webview/glue/glue.gni
+++ b/android_webview/glue/glue.gni
@@ -5,8 +5,9 @@
 # This variable shared between 'glue' and 'glue_resource_rewriter' because
 # ResourceRewrite.java need to be generated according 'glue' deps.
 glue_library_deps = [
-  "//android_webview:android_webview_java",
   "//android_webview:android_webview_commandline_java",
+  "//android_webview:android_webview_java",
+  "//android_webview:android_webview_locale_config_java",
   "//android_webview:android_webview_platform_services_java",
   "//android_webview:system_webview_manifest",
   "//android_webview/support_library/boundary_interfaces:boundary_interface_java",
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
index 1d286e2..2c32e31 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
@@ -25,6 +25,7 @@
 import org.chromium.android_webview.AwContentsStatics;
 import org.chromium.android_webview.AwCookieManager;
 import org.chromium.android_webview.AwFirebaseConfig;
+import org.chromium.android_webview.AwLocaleConfig;
 import org.chromium.android_webview.AwNetworkChangeNotifierRegistrationPolicy;
 import org.chromium.android_webview.AwProxyController;
 import org.chromium.android_webview.AwQuotaManagerBridge;
@@ -53,6 +54,7 @@
 import org.chromium.base.task.TaskTraits;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.net.NetworkChangeNotifier;
+import org.chromium.ui.base.ResourceBundle;
 
 /**
  * Class controlling the Chromium initialization for WebView.
@@ -140,6 +142,9 @@
 
             JNIUtils.setClassLoader(WebViewChromiumAwInit.class.getClassLoader());
 
+            ResourceBundle.setAvailablePakLocales(
+                    new String[] {}, AwLocaleConfig.getWebViewSupportedPakLocales());
+
             // We are rewriting Java resources in the background.
             // NOTE: Any reference to Java resources will cause a crash.
 
diff --git a/android_webview/java/src/org/chromium/android_webview/AwLocaleConfig.java b/android_webview/java/src/org/chromium/android_webview/AwLocaleConfig.java
new file mode 100644
index 0000000..a383ef0
--- /dev/null
+++ b/android_webview/java/src/org/chromium/android_webview/AwLocaleConfig.java
@@ -0,0 +1,17 @@
+// 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.
+
+package org.chromium.android_webview;
+
+/**
+ * Simple class that provides access to the array of uncompressed pak locales. See
+ * //android_webview/BUILD.gn for more details.
+ */
+public final class AwLocaleConfig {
+    private AwLocaleConfig() {}
+
+    public static String[] getWebViewSupportedPakLocales() {
+        return LocaleConfig.UNCOMPRESSED_LOCALES;
+    }
+}
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java
index efd9942..ade959e 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java
@@ -35,6 +35,7 @@
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer.OnPageFinishedHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.util.TestWebServer;
+import org.chromium.ui.base.ResourceBundle;
 
 import java.lang.annotation.Annotation;
 import java.util.Map;
@@ -83,6 +84,7 @@
     }
 
     public void setUp() throws Exception {
+        ResourceBundle.setAvailablePakLocales(new String[] {}, LocaleConfig.UNCOMPRESSED_LOCALES);
         if (needsAwBrowserContextCreated()) {
             createAwBrowserContext();
         }
diff --git a/android_webview/system_webview_apk_tmpl.gni b/android_webview/system_webview_apk_tmpl.gni
index 4d0bff5..aba533b 100644
--- a/android_webview/system_webview_apk_tmpl.gni
+++ b/android_webview/system_webview_apk_tmpl.gni
@@ -32,6 +32,8 @@
       "//base:base_java",
     ]
 
+    locale_config_java_packages = [ "org.chromium.android_webview" ]
+
     if (!defined(alternative_android_sdk_dep)) {
       alternative_android_sdk_dep = webview_framework_dep
     }
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index d062602..d154d4a 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -73,6 +73,7 @@
     "shell/src/org/chromium/android_webview/test/OnlyRunIn.java",
     "shell/src/org/chromium/android_webview/test/TestContentProvider.java",
   ]
+  locale_config_java_packages = [ "org.chromium.android_webview.test" ]
 
   shared_libraries = [ ":libstandalonelibwebviewchromium" ]
 
diff --git a/android_webview/tools/automated_ui_tests/javatests/AndroidManifest.xml b/android_webview/tools/automated_ui_tests/javatests/AndroidManifest.xml
index 9a683b9..62426e5 100644
--- a/android_webview/tools/automated_ui_tests/javatests/AndroidManifest.xml
+++ b/android_webview/tools/automated_ui_tests/javatests/AndroidManifest.xml
@@ -19,7 +19,7 @@
         <uses-library android:name="android.test.runner" />
     </application>
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="org.chromium.base.test.BaseChromiumAndroidJUnitRunner"
         android:targetPackage="org.chromium.webview_ui_test"
         android:label="Tests for org.chromium.webview_ui_test"/>
 </manifest>
diff --git a/android_webview/tools/system_webview_shell/layout_tests/AndroidManifest.xml b/android_webview/tools/system_webview_shell/layout_tests/AndroidManifest.xml
index f0198a6..1f584e4 100644
--- a/android_webview/tools/system_webview_shell/layout_tests/AndroidManifest.xml
+++ b/android_webview/tools/system_webview_shell/layout_tests/AndroidManifest.xml
@@ -30,7 +30,7 @@
             android:exported="true"/>
     </application>
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="org.chromium.base.test.BaseChromiumAndroidJUnitRunner"
         android:targetPackage="org.chromium.webview_shell"
         android:label="Layout tests for org.chromium.webview_shell"/>
 </manifest>
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index a684fcb..c10e464 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -332,8 +332,6 @@
     "host/transformer_helper.h",
     "ime/ime_controller.cc",
     "ime/ime_controller.h",
-    "ime/ime_engine_factory_registry.cc",
-    "ime/ime_engine_factory_registry.h",
     "ime/ime_mode_indicator_view.cc",
     "ime/ime_mode_indicator_view.h",
     "ime/ime_switch_type.h",
@@ -455,6 +453,9 @@
     "media/media_notification_background.h",
     "media/media_notification_constants.cc",
     "media/media_notification_constants.h",
+    "media/media_notification_container.h",
+    "media/media_notification_container_impl.cc",
+    "media/media_notification_container_impl.h",
     "media/media_notification_controller.h",
     "media/media_notification_controller_impl.cc",
     "media/media_notification_controller_impl.h",
@@ -765,8 +766,8 @@
     "system/network/network_tray_view.h",
     "system/network/sms_observer.cc",
     "system/network/sms_observer.h",
-    "system/network/tray_network_state_observer.cc",
-    "system/network/tray_network_state_observer.h",
+    "system/network/tray_network_state_model.cc",
+    "system/network/tray_network_state_model.h",
     "system/network/unified_network_detailed_view_controller.cc",
     "system/network/unified_network_detailed_view_controller.h",
     "system/network/unified_vpn_detailed_view_controller.cc",
@@ -1642,7 +1643,6 @@
     "home_screen/home_launcher_gesture_handler_unittest.cc",
     "home_screen/home_screen_controller_unittest.cc",
     "ime/ime_controller_unittest.cc",
-    "ime/ime_engine_factory_registry_unittest.cc",
     "keyboard/arc/arc_input_method_surface_manager_unittest.cc",
     "keyboard/ash_keyboard_controller_unittest.cc",
     "keyboard/virtual_keyboard_controller_unittest.cc",
@@ -1797,6 +1797,7 @@
     "system/unified/unified_system_info_view_unittest.cc",
     "system/unified/unified_system_tray_controller_unittest.cc",
     "system/unified/unified_system_tray_unittest.cc",
+    "system/unified/user_chooser_detailed_view_controller_unittest.cc",
     "system/update/update_notification_controller_unittest.cc",
     "system/virtual_keyboard/virtual_keyboard_tray_unittest.cc",
     "test/ash_test_helper_unittest.cc",
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index be990f8..5b9a2ec 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -99,6 +99,8 @@
 
 const char kNotifierAccelerator[] = "ash.accelerator-controller";
 
+const char kTabletCountOfVolumeAdjustType[] = "Tablet.CountOfVolumeAdjustType";
+
 const char kHighContrastToggleAccelNotificationId[] =
     "chrome://settings/accessibility/highcontrast";
 const char kDockedMagnifierToggleAccelNotificationId[] =
@@ -113,13 +115,21 @@
 using message_center::SystemNotificationWarningLevel;
 
 // Toast id and duration for voice interaction shortcuts
-const char kVoiceInteractionErrorToastId[] = "voice_interaction_error";
-const int kToastDurationMs = 2500;
+constexpr char kVoiceInteractionErrorToastId[] = "voice_interaction_error";
+constexpr int kToastDurationMs = 2500;
 
 // Path of the json file that contains side volume button location info.
-const char kSideVolumeButtonLocationFilePath[] =
+constexpr char kSideVolumeButtonLocationFilePath[] =
     "/usr/share/chromeos-assets/side_volume_button/location.json";
 
+// The interval between two volume control actions within one volume adjust.
+constexpr base::TimeDelta kVolumeAdjustTimeout =
+    base::TimeDelta::FromSeconds(2);
+
+void RecordTabletVolumeAdjustTypeHistogram(TabletModeVolumeAdjustType type) {
+  UMA_HISTOGRAM_ENUMERATION(kTabletCountOfVolumeAdjustType, type);
+}
+
 // Ensures that there are no word breaks at the "+"s in the shortcut texts such
 // as "Ctrl+Shift+Space".
 void EnsureNoWordBreaks(base::string16* shortcut_text) {
@@ -1062,6 +1072,53 @@
 ////////////////////////////////////////////////////////////////////////////////
 // AcceleratorControllerImpl, public:
 
+AcceleratorControllerImpl::TestApi::TestApi(
+    AcceleratorControllerImpl* controller)
+    : controller_(controller) {
+  DCHECK(controller_);
+}
+
+bool AcceleratorControllerImpl::TestApi::TriggerTabletModeVolumeAdjustTimer() {
+  if (!controller_->tablet_mode_volume_adjust_timer_.IsRunning())
+    return false;
+
+  controller_->tablet_mode_volume_adjust_timer_.FireNow();
+  return true;
+}
+
+void AcceleratorControllerImpl::TestApi::RegisterAccelerators(
+    const AcceleratorData accelerators[],
+    size_t accelerators_length) {
+  controller_->RegisterAccelerators(accelerators, accelerators_length);
+}
+
+const DeprecatedAcceleratorData*
+AcceleratorControllerImpl::TestApi::GetDeprecatedAcceleratorData(
+    AcceleratorAction action) {
+  auto it = controller_->actions_with_deprecations_.find(action);
+  if (it == controller_->actions_with_deprecations_.end())
+    return nullptr;
+
+  return it->second;
+}
+
+AcceleratorConfirmationDialog*
+AcceleratorControllerImpl::TestApi::GetConfirmationDialog() {
+  return controller_->confirmation_dialog_.get();
+}
+
+void AcceleratorControllerImpl::TestApi::SetSideVolumeButtonFilePath(
+    base::FilePath path) {
+  controller_->side_volume_button_location_file_path_ = path;
+}
+
+void AcceleratorControllerImpl::TestApi::SetSideVolumeButtonLocation(
+    const std::string& region,
+    const std::string& side) {
+  controller_->side_volume_button_location_.region = region;
+  controller_->side_volume_button_location_.side = side;
+}
+
 AcceleratorControllerImpl::AcceleratorControllerImpl()
     : accelerator_manager_(std::make_unique<ui::AcceleratorManager>()),
       accelerator_history_(std::make_unique<ui::AcceleratorHistory>()),
@@ -1446,8 +1503,13 @@
     return;
 
   if ((action == VOLUME_DOWN || action == VOLUME_UP) &&
-      ShouldSwapSideVolumeButtons(accelerator.source_device_id())) {
-    action = action == VOLUME_DOWN ? VOLUME_UP : VOLUME_DOWN;
+      Shell::Get()
+          ->tablet_mode_controller()
+          ->IsTabletModeWindowManagerEnabled()) {
+    if (ShouldSwapSideVolumeButtons(accelerator.source_device_id()))
+      action = action == VOLUME_DOWN ? VOLUME_UP : VOLUME_DOWN;
+
+    StartTabletModeVolumeAdjustTimer(action);
   }
 
   // If your accelerator invokes more than one line of code, please either
@@ -1935,9 +1997,6 @@
 bool AcceleratorControllerImpl::ShouldSwapSideVolumeButtons(
     int source_device_id) const {
   if (!features::IsSwapSideVolumeButtonsForOrientationEnabled() ||
-      !Shell::Get()
-           ->tablet_mode_controller()
-           ->IsTabletModeWindowManagerEnabled() ||
       !IsInternalKeyboardOrUncategorizedDevice(source_device_id)) {
     return false;
   }
@@ -1964,4 +2023,35 @@
   return is_landscape_secondary_or_portrait_primary;
 }
 
+void AcceleratorControllerImpl::UpdateTabletModeVolumeAdjustHistogram() {
+  const int volume_percent =
+      chromeos::CrasAudioHandler::Get()->GetOutputVolumePercent();
+  const bool swapped = features::IsSwapSideVolumeButtonsForOrientationEnabled();
+  if ((volume_adjust_starts_with_up_ &&
+       volume_percent >= initial_volume_percent_) ||
+      (!volume_adjust_starts_with_up_ &&
+       volume_percent <= initial_volume_percent_)) {
+    RecordTabletVolumeAdjustTypeHistogram(
+        swapped ? TabletModeVolumeAdjustType::kNormalAdjustWithSwapEnabled
+                : TabletModeVolumeAdjustType::kNormalAdjustWithSwapDisabled);
+  } else {
+    RecordTabletVolumeAdjustTypeHistogram(
+        swapped
+            ? TabletModeVolumeAdjustType::kAccidentalAdjustWithSwapEnabled
+            : TabletModeVolumeAdjustType::kAccidentalAdjustWithSwapDisabled);
+  }
+}
+
+void AcceleratorControllerImpl::StartTabletModeVolumeAdjustTimer(
+    AcceleratorAction action) {
+  if (!tablet_mode_volume_adjust_timer_.IsRunning()) {
+    volume_adjust_starts_with_up_ = action == VOLUME_UP;
+    initial_volume_percent_ =
+        chromeos::CrasAudioHandler::Get()->GetOutputVolumePercent();
+  }
+  tablet_mode_volume_adjust_timer_.Start(
+      FROM_HERE, kVolumeAdjustTimeout, this,
+      &AcceleratorControllerImpl::UpdateTabletModeVolumeAdjustHistogram);
+}
+
 }  // namespace ash
diff --git a/ash/accelerators/accelerator_controller_impl.h b/ash/accelerators/accelerator_controller_impl.h
index 6526988..4807125 100644
--- a/ash/accelerators/accelerator_controller_impl.h
+++ b/ash/accelerators/accelerator_controller_impl.h
@@ -32,6 +32,18 @@
 struct AcceleratorData;
 class ExitWarningHandler;
 
+// See TabletModeVolumeAdjustType at tools/metrics/histograms/enums.xml.
+enum class TabletModeVolumeAdjustType {
+  kAccidentalAdjustWithSwapEnabled = 0,
+  kNormalAdjustWithSwapEnabled = 1,
+  kAccidentalAdjustWithSwapDisabled = 2,
+  kNormalAdjustWithSwapDisabled = 3,
+  kMaxValue = kNormalAdjustWithSwapDisabled,
+};
+
+// Histogram for volume adjustment in tablet mode.
+ASH_EXPORT extern const char kTabletCountOfVolumeAdjustType[];
+
 // Identifiers for toggling accelerator notifications.
 ASH_EXPORT extern const char kHighContrastToggleAccelNotificationId[];
 ASH_EXPORT extern const char kDockedMagnifierToggleAccelNotificationId[];
@@ -43,6 +55,57 @@
 class ASH_EXPORT AcceleratorControllerImpl : public ui::AcceleratorTarget,
                                              public AcceleratorController {
  public:
+  // Some Chrome OS devices have volume up and volume down buttons on their
+  // side. We want the button that's closer to the top/right to increase the
+  // volume and the button that's closer to the bottom/left to decrease the
+  // volume, so we use the buttons' location and the device orientation to
+  // determine whether the buttons should be swapped.
+  struct SideVolumeButtonLocation {
+    // The button can be at the side of the keyboard or the display. Then value
+    // of the region could be kVolumeButtonRegionKeyboard or
+    // kVolumeButtonRegionScreen.
+    std::string region;
+    // Side info of region. The value could be kVolumeButtonSideLeft,
+    // kVolumeButtonSideRight, kVolumeButtonSideTop or kVolumeButtonSideBottom.
+    std::string side;
+  };
+
+  // TestApi is used for tests to get internal implementation details.
+  class TestApi {
+   public:
+    explicit TestApi(AcceleratorControllerImpl* controller);
+    ~TestApi() = default;
+
+    // If |controller_->tablet_mode_volume_adjust_timer_| is running, stops it,
+    // runs its task, and returns true. Otherwise returns false.
+    bool TriggerTabletModeVolumeAdjustTimer() WARN_UNUSED_RESULT;
+
+    // Registers the specified accelerators.
+    void RegisterAccelerators(const AcceleratorData accelerators[],
+                              size_t accelerators_length);
+
+    // Returns the corresponding accelerator data if |action| maps to a
+    // deprecated accelerator, otherwise return nullptr.
+    const DeprecatedAcceleratorData* GetDeprecatedAcceleratorData(
+        AcceleratorAction action);
+
+    // Accessor to accelerator confirmation dialog.
+    AcceleratorConfirmationDialog* GetConfirmationDialog();
+
+    AcceleratorControllerImpl::SideVolumeButtonLocation
+    side_volume_button_location() {
+      return controller_->side_volume_button_location_;
+    }
+    void SetSideVolumeButtonFilePath(base::FilePath path);
+    void SetSideVolumeButtonLocation(const std::string& region,
+                                     const std::string& side);
+
+   private:
+    AcceleratorControllerImpl* controller_;  // Not owned.
+
+    DISALLOW_COPY_AND_ASSIGN(TestApi);
+  };
+
   // Fields of the side volume button location info.
   static constexpr const char* kVolumeButtonRegion = "region";
   static constexpr const char* kVolumeButtonSide = "side";
@@ -74,21 +137,6 @@
     RESTRICTION_PREVENT_PROCESSING_AND_PROPAGATION
   };
 
-  // Some Chrome OS devices have volume up and volume down buttons on their
-  // side. We want the button that's closer to the top/right to increase the
-  // volume and the button that's closer to the bottom/left to decrease the
-  // volume, so we use the buttons' location and the device orientation to
-  // determine whether the buttons should be swapped.
-  struct SideVolumeButtonLocation {
-    // The button can be at the side of the keyboard or the display. Then value
-    // of the region could be kVolumeButtonRegionKeyboard or
-    // kVolumeButtonRegionScreen.
-    std::string region;
-    // Side info of region. The value could be kVolumeButtonSideLeft,
-    // kVolumeButtonSideRight, kVolumeButtonSideTop or kVolumeButtonSideBottom.
-    std::string side;
-  };
-
   // Registers global keyboard accelerators for the specified target. If
   // multiple targets are registered for any given accelerator, a target
   // registered later has higher priority.
@@ -158,30 +206,7 @@
   // |side_volume_button_location_|.
   void ParseSideVolumeButtonLocationInfo();
 
-  // Accessor to accelerator confirmation dialog.
-  AcceleratorConfirmationDialog* confirmation_dialog_for_testing() {
-    return confirmation_dialog_.get();
-  }
-
-  void set_side_volume_button_file_path_for_testing(base::FilePath path) {
-    side_volume_button_location_file_path_ = path;
-  }
-  void set_side_volume_button_location_for_testing(const std::string& region,
-                                                   const std::string& side) {
-    side_volume_button_location_.region = region;
-    side_volume_button_location_.side = side;
-  }
-  SideVolumeButtonLocation side_volume_button_location_for_testing() {
-    return side_volume_button_location_;
-  }
-
  private:
-  FRIEND_TEST_ALL_PREFIXES(AcceleratorControllerTest, GlobalAccelerators);
-  FRIEND_TEST_ALL_PREFIXES(AcceleratorControllerTest,
-                           DontRepeatToggleFullscreen);
-  FRIEND_TEST_ALL_PREFIXES(DeprecatedAcceleratorTester,
-                           TestDeprecatedAcceleratorsBehavior);
-
   // Initializes the accelerators this class handles as a target.
   void Init();
 
@@ -233,6 +258,17 @@
   // SideVolumeButonLocation for the details.
   bool ShouldSwapSideVolumeButtons(int source_device_id) const;
 
+  // The metrics recorded include accidental volume adjustments (defined as a
+  // sequence of volume button events in close succession starting with a
+  // volume-up event but ending with an overall-decreased volume, or vice versa)
+  // or normal volume adjustments w/o SwapSideVolumeButtonsForOrientation
+  // feature enabled.
+  void UpdateTabletModeVolumeAdjustHistogram();
+
+  // Starts |tablet_mode_volume_adjust_timer_| while see VOLUME_UP or
+  // VOLUME_DOWN acceleration action when in tablet mode.
+  void StartTabletModeVolumeAdjustTimer(AcceleratorAction action);
+
   std::unique_ptr<ui::AcceleratorManager> accelerator_manager_;
 
   // A tracker for the current and previous accelerators.
@@ -286,6 +322,17 @@
   // Stores the location info of side volume buttons.
   SideVolumeButtonLocation side_volume_button_location_;
 
+  // Started when VOLUME_DOWN or VOLUME_UP accelerator action is seen while in
+  // tablet mode. Runs UpdateTabletModeVolumeAdjustHistogram() to record
+  // metrics.
+  base::OneShotTimer tablet_mode_volume_adjust_timer_;
+
+  // True if volume adjust starts with VOLUME_UP action.
+  bool volume_adjust_starts_with_up_ = false;
+
+  // The initial volume percentage when volume adjust starts.
+  int initial_volume_percent_ = 0;
+
   DISALLOW_COPY_AND_ASSIGN(AcceleratorControllerImpl);
 };
 
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc
index c395952..d2d8272 100644
--- a/ash/accelerators/accelerator_controller_unittest.cc
+++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -180,22 +180,28 @@
   AcceleratorControllerTest() = default;
   ~AcceleratorControllerTest() override = default;
 
- protected:
-  static AcceleratorControllerImpl* GetController();
+  void SetUp() override {
+    AshTestBase::SetUp();
+    controller_ = Shell::Get()->accelerator_controller();
+    test_api_ =
+        std::make_unique<AcceleratorControllerImpl::TestApi>(controller_);
+  }
 
+ protected:
   static bool ProcessInController(const ui::Accelerator& accelerator) {
+    AcceleratorControllerImpl* controller =
+        Shell::Get()->accelerator_controller();
     if (accelerator.key_state() == ui::Accelerator::KeyState::RELEASED) {
       // If the |accelerator| should trigger on release, then we store the
       // pressed version of it first in history then the released one to
       // simulate what happens in reality.
       ui::Accelerator pressed_accelerator = accelerator;
       pressed_accelerator.set_key_state(ui::Accelerator::KeyState::PRESSED);
-      GetController()->accelerator_history()->StoreCurrentAccelerator(
+      controller->accelerator_history()->StoreCurrentAccelerator(
           pressed_accelerator);
     }
-    GetController()->accelerator_history()->StoreCurrentAccelerator(
-        accelerator);
-    return GetController()->Process(accelerator);
+    controller->accelerator_history()->StoreCurrentAccelerator(accelerator);
+    return controller->Process(accelerator);
   }
 
   bool ContainsHighContrastNotification() const {
@@ -214,23 +220,17 @@
   }
 
   bool IsConfirmationDialogOpen() {
-    return !!(GetController()->confirmation_dialog_for_testing());
+    return !!(test_api_->GetConfirmationDialog());
   }
 
   void AcceptConfirmationDialog() {
-    DCHECK(GetController()->confirmation_dialog_for_testing());
-    GetController()
-        ->confirmation_dialog_for_testing()
-        ->GetDialogClientView()
-        ->AcceptWindow();
+    DCHECK(test_api_->GetConfirmationDialog());
+    test_api_->GetConfirmationDialog()->GetDialogClientView()->AcceptWindow();
   }
 
   void CancelConfirmationDialog() {
-    DCHECK(GetController()->confirmation_dialog_for_testing());
-    GetController()
-        ->confirmation_dialog_for_testing()
-        ->GetDialogClientView()
-        ->CancelWindow();
+    DCHECK(test_api_->GetConfirmationDialog());
+    test_api_->GetConfirmationDialog()->GetDialogClientView()->CancelWindow();
   }
 
   void RemoveAllNotifications() const {
@@ -239,11 +239,17 @@
   }
 
   static const ui::Accelerator& GetPreviousAccelerator() {
-    return GetController()->accelerator_history()->previous_accelerator();
+    return Shell::Get()
+        ->accelerator_controller()
+        ->accelerator_history()
+        ->previous_accelerator();
   }
 
   static const ui::Accelerator& GetCurrentAccelerator() {
-    return GetController()->accelerator_history()->current_accelerator();
+    return Shell::Get()
+        ->accelerator_controller()
+        ->accelerator_history()
+        ->current_accelerator();
   }
 
   // Several functions to access ExitWarningHandler (as friend).
@@ -294,20 +300,19 @@
     return true;
   }
 
+  AcceleratorControllerImpl* controller_ = nullptr;  // Not owned.
+  std::unique_ptr<AcceleratorControllerImpl::TestApi> test_api_;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(AcceleratorControllerTest);
 };
 
-AcceleratorControllerImpl* AcceleratorControllerTest::GetController() {
-  return Shell::Get()->accelerator_controller();
-}
-
 // Double press of exit shortcut => exiting
 TEST_F(AcceleratorControllerTest, ExitWarningHandlerTestDoublePress) {
   ui::Accelerator press(ui::VKEY_Q, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
   ui::Accelerator release(press);
   release.set_key_state(ui::Accelerator::KeyState::RELEASED);
-  ExitWarningHandler* ewh = GetController()->GetExitWarningHandlerForTest();
+  ExitWarningHandler* ewh = controller_->GetExitWarningHandlerForTest();
   ASSERT_TRUE(ewh);
   StubForTest(ewh);
   EXPECT_TRUE(is_idle(ewh));
@@ -329,7 +334,7 @@
   ui::Accelerator press(ui::VKEY_Q, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
   ui::Accelerator release(press);
   release.set_key_state(ui::Accelerator::KeyState::RELEASED);
-  ExitWarningHandler* ewh = GetController()->GetExitWarningHandlerForTest();
+  ExitWarningHandler* ewh = controller_->GetExitWarningHandlerForTest();
   ASSERT_TRUE(ewh);
   StubForTest(ewh);
   EXPECT_TRUE(is_idle(ewh));
@@ -346,7 +351,7 @@
 
 // Shutdown ash with exit warning bubble open should not crash.
 TEST_F(AcceleratorControllerTest, LingeringExitWarningBubble) {
-  ExitWarningHandler* ewh = GetController()->GetExitWarningHandlerForTest();
+  ExitWarningHandler* ewh = controller_->GetExitWarningHandlerForTest();
   ASSERT_TRUE(ewh);
   StubForTest(ewh);
 
@@ -365,7 +370,7 @@
   const ui::Accelerator accelerator_c(ui::VKEY_C, ui::EF_NONE);
   const ui::Accelerator accelerator_d(ui::VKEY_D, ui::EF_NONE);
 
-  GetController()->Register(
+  controller_->Register(
       {accelerator_a, accelerator_b, accelerator_c, accelerator_d}, &target);
 
   // The registered accelerators are processed.
@@ -379,9 +384,9 @@
 TEST_F(AcceleratorControllerTest, RegisterMultipleTarget) {
   const ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
   ui::TestAcceleratorTarget target1;
-  GetController()->Register({accelerator_a}, &target1);
+  controller_->Register({accelerator_a}, &target1);
   ui::TestAcceleratorTarget target2;
-  GetController()->Register({accelerator_a}, &target2);
+  controller_->Register({accelerator_a}, &target2);
 
   // If multiple targets are registered with the same accelerator, the target
   // registered later processes the accelerator.
@@ -394,17 +399,17 @@
   const ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
   const ui::Accelerator accelerator_b(ui::VKEY_B, ui::EF_NONE);
   ui::TestAcceleratorTarget target;
-  GetController()->Register({accelerator_a, accelerator_b}, &target);
+  controller_->Register({accelerator_a, accelerator_b}, &target);
 
   // Unregistering a different accelerator does not affect the other
   // accelerator.
-  GetController()->Unregister(accelerator_b, &target);
+  controller_->Unregister(accelerator_b, &target);
   EXPECT_TRUE(ProcessInController(accelerator_a));
   EXPECT_EQ(1, target.accelerator_count());
 
   // The unregistered accelerator is no longer processed.
   target.ResetCounts();
-  GetController()->Unregister(accelerator_a, &target);
+  controller_->Unregister(accelerator_a, &target);
   EXPECT_FALSE(ProcessInController(accelerator_a));
   EXPECT_EQ(0, target.accelerator_count());
 }
@@ -413,11 +418,11 @@
   const ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
   const ui::Accelerator accelerator_b(ui::VKEY_B, ui::EF_NONE);
   ui::TestAcceleratorTarget target1;
-  GetController()->Register({accelerator_a, accelerator_b}, &target1);
+  controller_->Register({accelerator_a, accelerator_b}, &target1);
   const ui::Accelerator accelerator_c(ui::VKEY_C, ui::EF_NONE);
   ui::TestAcceleratorTarget target2;
-  GetController()->Register({accelerator_c}, &target2);
-  GetController()->UnregisterAll(&target1);
+  controller_->Register({accelerator_c}, &target2);
+  controller_->UnregisterAll(&target1);
 
   // All the accelerators registered for |target1| are no longer processed.
   EXPECT_FALSE(ProcessInController(accelerator_a));
@@ -432,7 +437,7 @@
 TEST_F(AcceleratorControllerTest, Process) {
   const ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
   ui::TestAcceleratorTarget target1;
-  GetController()->Register({accelerator_a}, &target1);
+  controller_->Register({accelerator_a}, &target1);
 
   // The registered accelerator is processed.
   EXPECT_TRUE(ProcessInController(accelerator_a));
@@ -447,11 +452,11 @@
   const ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
   const ui::Accelerator accelerator_shift_a(ui::VKEY_A, ui::EF_SHIFT_DOWN);
   ui::TestAcceleratorTarget target;
-  GetController()->Register({accelerator_a}, &target);
-  EXPECT_TRUE(GetController()->IsRegistered(accelerator_a));
-  EXPECT_FALSE(GetController()->IsRegistered(accelerator_shift_a));
-  GetController()->UnregisterAll(&target);
-  EXPECT_FALSE(GetController()->IsRegistered(accelerator_a));
+  controller_->Register({accelerator_a}, &target);
+  EXPECT_TRUE(controller_->IsRegistered(accelerator_a));
+  EXPECT_FALSE(controller_->IsRegistered(accelerator_shift_a));
+  controller_->UnregisterAll(&target);
+  EXPECT_FALSE(controller_->IsRegistered(accelerator_a));
 }
 
 TEST_F(AcceleratorControllerTest, WindowSnap) {
@@ -462,13 +467,13 @@
   window_state->Activate();
 
   {
-    GetController()->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_LEFT, {});
+    controller_->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_LEFT, {});
     gfx::Rect expected_bounds =
         wm::GetDefaultLeftSnappedWindowBoundsInParent(window.get());
     EXPECT_EQ(expected_bounds.ToString(), window->bounds().ToString());
   }
   {
-    GetController()->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_RIGHT, {});
+    controller_->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_RIGHT, {});
     gfx::Rect expected_bounds =
         wm::GetDefaultRightSnappedWindowBoundsInParent(window.get());
     EXPECT_EQ(expected_bounds.ToString(), window->bounds().ToString());
@@ -476,34 +481,34 @@
   {
     gfx::Rect normal_bounds = window_state->GetRestoreBoundsInParent();
 
-    GetController()->PerformActionIfEnabled(TOGGLE_MAXIMIZED, {});
+    controller_->PerformActionIfEnabled(TOGGLE_MAXIMIZED, {});
     EXPECT_TRUE(window_state->IsMaximized());
     EXPECT_NE(normal_bounds.ToString(), window->bounds().ToString());
 
-    GetController()->PerformActionIfEnabled(TOGGLE_MAXIMIZED, {});
+    controller_->PerformActionIfEnabled(TOGGLE_MAXIMIZED, {});
     EXPECT_FALSE(window_state->IsMaximized());
     // Window gets restored to its restore bounds since side-maximized state
     // is treated as a "maximized" state.
     EXPECT_EQ(normal_bounds.ToString(), window->bounds().ToString());
 
-    GetController()->PerformActionIfEnabled(TOGGLE_MAXIMIZED, {});
-    GetController()->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_LEFT, {});
+    controller_->PerformActionIfEnabled(TOGGLE_MAXIMIZED, {});
+    controller_->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_LEFT, {});
     EXPECT_FALSE(window_state->IsMaximized());
 
-    GetController()->PerformActionIfEnabled(TOGGLE_MAXIMIZED, {});
-    GetController()->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_RIGHT, {});
+    controller_->PerformActionIfEnabled(TOGGLE_MAXIMIZED, {});
+    controller_->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_RIGHT, {});
     EXPECT_FALSE(window_state->IsMaximized());
 
-    GetController()->PerformActionIfEnabled(TOGGLE_MAXIMIZED, {});
+    controller_->PerformActionIfEnabled(TOGGLE_MAXIMIZED, {});
     EXPECT_TRUE(window_state->IsMaximized());
-    GetController()->PerformActionIfEnabled(WINDOW_MINIMIZE, {});
+    controller_->PerformActionIfEnabled(WINDOW_MINIMIZE, {});
     EXPECT_FALSE(window_state->IsMaximized());
     EXPECT_TRUE(window_state->IsMinimized());
     window_state->Restore();
     window_state->Activate();
   }
   {
-    GetController()->PerformActionIfEnabled(WINDOW_MINIMIZE, {});
+    controller_->PerformActionIfEnabled(WINDOW_MINIMIZE, {});
     EXPECT_TRUE(window_state->IsMinimized());
   }
 }
@@ -517,26 +522,26 @@
   window_state->Activate();
 
   // Snap right.
-  GetController()->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_RIGHT, {});
+  controller_->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_RIGHT, {});
   gfx::Rect normal_bounds = window_state->GetRestoreBoundsInParent();
   gfx::Rect expected_bounds =
       wm::GetDefaultRightSnappedWindowBoundsInParent(window.get());
   EXPECT_EQ(expected_bounds.ToString(), window->bounds().ToString());
   EXPECT_TRUE(window_state->IsSnapped());
   // Snap right again ->> becomes normal.
-  GetController()->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_RIGHT, {});
+  controller_->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_RIGHT, {});
   EXPECT_TRUE(window_state->IsNormalStateType());
   EXPECT_EQ(normal_bounds.ToString(), window->bounds().ToString());
   // Snap right.
-  GetController()->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_RIGHT, {});
+  controller_->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_RIGHT, {});
   EXPECT_TRUE(window_state->IsSnapped());
   // Snap left.
-  GetController()->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_LEFT, {});
+  controller_->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_LEFT, {});
   EXPECT_TRUE(window_state->IsSnapped());
   expected_bounds = wm::GetDefaultLeftSnappedWindowBoundsInParent(window.get());
   EXPECT_EQ(expected_bounds.ToString(), window->bounds().ToString());
   // Snap left again ->> becomes normal.
-  GetController()->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_LEFT, {});
+  controller_->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_LEFT, {});
   EXPECT_TRUE(window_state->IsNormalStateType());
   EXPECT_EQ(normal_bounds.ToString(), window->bounds().ToString());
 }
@@ -590,10 +595,10 @@
 TEST_F(AcceleratorControllerTest, AutoRepeat) {
   ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_CONTROL_DOWN);
   ui::TestAcceleratorTarget target_a;
-  GetController()->Register({accelerator_a}, &target_a);
+  controller_->Register({accelerator_a}, &target_a);
   ui::Accelerator accelerator_b(ui::VKEY_B, ui::EF_CONTROL_DOWN);
   ui::TestAcceleratorTarget target_b;
-  GetController()->Register({accelerator_b}, &target_b);
+  controller_->Register({accelerator_b}, &target_b);
 
   ui::test::EventGenerator* generator = GetEventGenerator();
   generator->PressKey(ui::VKEY_A, ui::EF_CONTROL_DOWN);
@@ -649,7 +654,7 @@
       {true, ui::VKEY_J, ui::EF_ALT_DOWN, TOGGLE_FULLSCREEN},
       {true, ui::VKEY_K, ui::EF_ALT_DOWN, TOGGLE_FULLSCREEN},
   };
-  GetController()->RegisterAccelerators(accelerators, base::size(accelerators));
+  test_api_->RegisterAccelerators(accelerators, base::size(accelerators));
 
   views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
   params.bounds = gfx::Rect(5, 5, 20, 20);
@@ -684,7 +689,7 @@
   DisableIME();
   ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
   ui::TestAcceleratorTarget target;
-  GetController()->Register({accelerator_a}, &target);
+  controller_->Register({accelerator_a}, &target);
 
   // The accelerator is processed only once.
   ui::EventSink* sink = Shell::GetPrimaryRootWindow()->GetHost()->event_sink();
@@ -743,7 +748,7 @@
   const ui::Accelerator volume_up(ui::VKEY_VOLUME_UP, ui::EF_NONE);
   {
     base::UserActionTester user_action_tester;
-    ui::AcceleratorHistory* history = GetController()->accelerator_history();
+    ui::AcceleratorHistory* history = controller_->accelerator_history();
 
     EXPECT_EQ(0, user_action_tester.GetActionCount("Accel_VolumeMute_F8"));
     EXPECT_TRUE(ProcessInController(volume_mute));
@@ -802,7 +807,7 @@
   }
 
   // Exit
-  ExitWarningHandler* ewh = GetController()->GetExitWarningHandlerForTest();
+  ExitWarningHandler* ewh = controller_->GetExitWarningHandlerForTest();
   ASSERT_TRUE(ewh);
   StubForTest(ewh);
   EXPECT_TRUE(is_idle(ewh));
@@ -852,7 +857,7 @@
 }
 
 TEST_F(AcceleratorControllerTest, GlobalAcceleratorsToggleAppList) {
-  AccessibilityController* controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
 
   // The press event should not toggle the AppList, the release should instead.
@@ -869,14 +874,16 @@
   EXPECT_EQ(ui::VKEY_LWIN, GetPreviousAccelerator().key_code());
 
   // When spoken feedback is on, the AppList should not toggle.
-  controller->SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_NONE);
-  EXPECT_TRUE(controller->spoken_feedback_enabled());
+  accessibility_controller->SetSpokenFeedbackEnabled(true,
+                                                     A11Y_NOTIFICATION_NONE);
+  EXPECT_TRUE(accessibility_controller->spoken_feedback_enabled());
   EXPECT_FALSE(
       ProcessInController(ui::Accelerator(ui::VKEY_LWIN, ui::EF_NONE)));
   EXPECT_FALSE(ProcessInController(
       CreateReleaseAccelerator(ui::VKEY_LWIN, ui::EF_NONE)));
-  controller->SetSpokenFeedbackEnabled(false, A11Y_NOTIFICATION_NONE);
-  EXPECT_FALSE(controller->spoken_feedback_enabled());
+  accessibility_controller->SetSpokenFeedbackEnabled(false,
+                                                     A11Y_NOTIFICATION_NONE);
+  EXPECT_FALSE(accessibility_controller->spoken_feedback_enabled());
   base::RunLoop().RunUntilIdle();
   GetAppListTestHelper()->CheckVisibility(true);
 
@@ -901,7 +908,7 @@
   // When pressed key is interrupted by mouse, the AppList should not toggle.
   EXPECT_FALSE(
       ProcessInController(ui::Accelerator(ui::VKEY_LWIN, ui::EF_NONE)));
-  GetController()->accelerator_history()->InterruptCurrentAccelerator();
+  controller_->accelerator_history()->InterruptCurrentAccelerator();
   EXPECT_FALSE(ProcessInController(
       CreateReleaseAccelerator(ui::VKEY_LWIN, ui::EF_NONE)));
   base::RunLoop().RunUntilIdle();
@@ -1035,44 +1042,41 @@
 
 TEST_F(AcceleratorControllerTest, PreferredReservedAccelerators) {
   // Power key is reserved on chromeos.
-  EXPECT_TRUE(GetController()->IsReserved(
-      ui::Accelerator(ui::VKEY_POWER, ui::EF_NONE)));
-  EXPECT_FALSE(GetController()->IsPreferred(
-      ui::Accelerator(ui::VKEY_POWER, ui::EF_NONE)));
+  EXPECT_TRUE(
+      controller_->IsReserved(ui::Accelerator(ui::VKEY_POWER, ui::EF_NONE)));
+  EXPECT_FALSE(
+      controller_->IsPreferred(ui::Accelerator(ui::VKEY_POWER, ui::EF_NONE)));
 
   // ALT+Tab are not reserved but preferred.
-  EXPECT_FALSE(GetController()->IsReserved(
-      ui::Accelerator(ui::VKEY_TAB, ui::EF_ALT_DOWN)));
-  EXPECT_FALSE(GetController()->IsReserved(
+  EXPECT_FALSE(
+      controller_->IsReserved(ui::Accelerator(ui::VKEY_TAB, ui::EF_ALT_DOWN)));
+  EXPECT_FALSE(controller_->IsReserved(
       ui::Accelerator(ui::VKEY_TAB, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN)));
-  EXPECT_TRUE(GetController()->IsPreferred(
-      ui::Accelerator(ui::VKEY_TAB, ui::EF_ALT_DOWN)));
-  EXPECT_TRUE(GetController()->IsPreferred(
+  EXPECT_TRUE(
+      controller_->IsPreferred(ui::Accelerator(ui::VKEY_TAB, ui::EF_ALT_DOWN)));
+  EXPECT_TRUE(controller_->IsPreferred(
       ui::Accelerator(ui::VKEY_TAB, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN)));
 
   // Others are not reserved nor preferred
-  EXPECT_FALSE(GetController()->IsReserved(
-      ui::Accelerator(ui::VKEY_SNAPSHOT, ui::EF_NONE)));
-  EXPECT_FALSE(GetController()->IsPreferred(
+  EXPECT_FALSE(
+      controller_->IsReserved(ui::Accelerator(ui::VKEY_SNAPSHOT, ui::EF_NONE)));
+  EXPECT_FALSE(controller_->IsPreferred(
       ui::Accelerator(ui::VKEY_SNAPSHOT, ui::EF_NONE)));
   EXPECT_FALSE(
-      GetController()->IsReserved(ui::Accelerator(ui::VKEY_TAB, ui::EF_NONE)));
+      controller_->IsReserved(ui::Accelerator(ui::VKEY_TAB, ui::EF_NONE)));
   EXPECT_FALSE(
-      GetController()->IsPreferred(ui::Accelerator(ui::VKEY_TAB, ui::EF_NONE)));
+      controller_->IsPreferred(ui::Accelerator(ui::VKEY_TAB, ui::EF_NONE)));
   EXPECT_FALSE(
-      GetController()->IsReserved(ui::Accelerator(ui::VKEY_A, ui::EF_NONE)));
+      controller_->IsReserved(ui::Accelerator(ui::VKEY_A, ui::EF_NONE)));
   EXPECT_FALSE(
-      GetController()->IsPreferred(ui::Accelerator(ui::VKEY_A, ui::EF_NONE)));
+      controller_->IsPreferred(ui::Accelerator(ui::VKEY_A, ui::EF_NONE)));
 }
 
 TEST_F(AcceleratorControllerTest, SideVolumeButtonLocation) {
   // |side_volume_button_location_| should be empty when location info file
   // doesn't exist.
-  EXPECT_TRUE(GetController()
-                  ->side_volume_button_location_for_testing()
-                  .region.empty());
-  EXPECT_TRUE(
-      GetController()->side_volume_button_location_for_testing().side.empty());
+  EXPECT_TRUE(test_api_->side_volume_button_location().region.empty());
+  EXPECT_TRUE(test_api_->side_volume_button_location().side.empty());
 
   // Tests that |side_volume_button_location_| is read correctly if the location
   // file exists.
@@ -1088,15 +1092,71 @@
   base::FilePath file_path = file_tmp_dir.GetPath().Append("location.json");
   ASSERT_TRUE(WriteJsonFile(file_path, json_location));
   EXPECT_TRUE(base::PathExists(file_path));
-  GetController()->set_side_volume_button_file_path_for_testing(file_path);
-  GetController()->ParseSideVolumeButtonLocationInfo();
+  test_api_->SetSideVolumeButtonFilePath(file_path);
+  controller_->ParseSideVolumeButtonLocationInfo();
   EXPECT_EQ(AcceleratorControllerImpl::kVolumeButtonRegionScreen,
-            GetController()->side_volume_button_location_for_testing().region);
+            test_api_->side_volume_button_location().region);
   EXPECT_EQ(AcceleratorControllerImpl::kVolumeButtonSideLeft,
-            GetController()->side_volume_button_location_for_testing().side);
+            test_api_->side_volume_button_location().side);
   base::DeleteFile(file_path, false);
 }
 
+// Tests the histogram of volume adjustment in tablet mode.
+TEST_F(AcceleratorControllerTest, TabletModeVolumeAdjustHistogram) {
+  Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
+  base::HistogramTester histogram_tester;
+  EXPECT_TRUE(
+      histogram_tester.GetAllSamples(kTabletCountOfVolumeAdjustType).empty());
+  const ui::Accelerator kVolumeDown(ui::VKEY_VOLUME_DOWN, ui::EF_NONE);
+  const ui::Accelerator kVolumeUp(ui::VKEY_VOLUME_UP, ui::EF_NONE);
+  ASSERT_FALSE(features::IsSwapSideVolumeButtonsForOrientationEnabled());
+  // Starts with volume down but ends with an overall-increased volume when
+  // features::kSwapSideVolumeButtonsForOrientation is disabled.
+  ProcessInController(kVolumeDown);
+  ProcessInController(kVolumeUp);
+  ProcessInController(kVolumeUp);
+  EXPECT_TRUE(test_api_->TriggerTabletModeVolumeAdjustTimer());
+  EXPECT_FALSE(
+      histogram_tester.GetAllSamples(kTabletCountOfVolumeAdjustType).empty());
+  histogram_tester.ExpectBucketCount(
+      kTabletCountOfVolumeAdjustType,
+      TabletModeVolumeAdjustType::kAccidentalAdjustWithSwapDisabled, 1);
+
+  // Starts with volume up and ends with an overall-increased volume when
+  // features::kSwapSideVolumeButtonsForOrientation is disabled.
+  ProcessInController(kVolumeUp);
+  ProcessInController(kVolumeUp);
+  ProcessInController(kVolumeUp);
+  EXPECT_TRUE(test_api_->TriggerTabletModeVolumeAdjustTimer());
+  histogram_tester.ExpectBucketCount(
+      kTabletCountOfVolumeAdjustType,
+      TabletModeVolumeAdjustType::kNormalAdjustWithSwapDisabled, 1);
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      features::kSwapSideVolumeButtonsForOrientation);
+  EXPECT_TRUE(features::IsSwapSideVolumeButtonsForOrientationEnabled());
+  // Starts with volume up but ends with an overall-decreased volume when
+  // features::kSwapSideVolumeButtonsForOrientation is enabled.
+  ProcessInController(kVolumeUp);
+  ProcessInController(kVolumeDown);
+  ProcessInController(kVolumeDown);
+  EXPECT_TRUE(test_api_->TriggerTabletModeVolumeAdjustTimer());
+  histogram_tester.ExpectBucketCount(
+      kTabletCountOfVolumeAdjustType,
+      TabletModeVolumeAdjustType::kAccidentalAdjustWithSwapEnabled, 1);
+
+  // Starts with volume up and ends with an overall-increased volume when
+  // features::kSwapSideVolumeButtonsForOrientation is enabled.
+  ProcessInController(kVolumeUp);
+  ProcessInController(kVolumeUp);
+  ProcessInController(kVolumeUp);
+  EXPECT_TRUE(test_api_->TriggerTabletModeVolumeAdjustTimer());
+  histogram_tester.ExpectBucketCount(
+      kTabletCountOfVolumeAdjustType,
+      TabletModeVolumeAdjustType::kNormalAdjustWithSwapEnabled, 1);
+}
+
 class SideVolumeButtonAcceleratorTest
     : public AcceleratorControllerTest,
       public testing::WithParamInterface<std::pair<std::string, std::string>> {
@@ -1111,8 +1171,7 @@
   void SetUp() override {
     AcceleratorControllerTest::SetUp();
     Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
-    GetController()->set_side_volume_button_location_for_testing(region_,
-                                                                 side_);
+    test_api_->SetSideVolumeButtonLocation(region_, side_);
     ws::InputDeviceClientTestApi().SetUncategorizedDevices({ui::InputDevice(
         kSideVolumeButtonId, ui::InputDeviceType::INPUT_DEVICE_INTERNAL,
         "cros_ec_buttons")});
@@ -1488,7 +1547,7 @@
   for (const auto& action : all_actions) {
     if (actionsAllowedAtModalWindow.find(action) ==
         actionsAllowedAtModalWindow.end()) {
-      EXPECT_TRUE(GetController()->PerformActionIfEnabled(action, {}))
+      EXPECT_TRUE(controller_->PerformActionIfEnabled(action, {}))
           << " for action (disallowed at modal window): " << action;
     }
   }
@@ -1540,7 +1599,7 @@
   const ui::Accelerator volume_up(ui::VKEY_VOLUME_UP, ui::EF_NONE);
   {
     base::UserActionTester user_action_tester;
-    ui::AcceleratorHistory* history = GetController()->accelerator_history();
+    ui::AcceleratorHistory* history = controller_->accelerator_history();
 
     EXPECT_EQ(0, user_action_tester.GetActionCount("Accel_VolumeMute_F8"));
     EXPECT_TRUE(ProcessInController(volume_mute));
@@ -1561,16 +1620,17 @@
 
 TEST_F(AcceleratorControllerTest, DisallowedWithNoWindow) {
   TestAccessibilityControllerClient client;
-  AccessibilityController* controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
-  controller->SetClient(client.CreateInterfacePtrAndBind());
+  accessibility_controller->SetClient(client.CreateInterfacePtrAndBind());
 
   for (size_t i = 0; i < kActionsNeedingWindowLength; ++i) {
-    controller->TriggerAccessibilityAlert(mojom::AccessibilityAlert::NONE);
-    controller->FlushMojoForTest();
+    accessibility_controller->TriggerAccessibilityAlert(
+        mojom::AccessibilityAlert::NONE);
+    accessibility_controller->FlushMojoForTest();
     EXPECT_TRUE(
-        GetController()->PerformActionIfEnabled(kActionsNeedingWindow[i], {}));
-    controller->FlushMojoForTest();
+        controller_->PerformActionIfEnabled(kActionsNeedingWindow[i], {}));
+    accessibility_controller->FlushMojoForTest();
     EXPECT_EQ(mojom::AccessibilityAlert::WINDOW_NEEDED,
               client.last_a11y_alert());
   }
@@ -1580,10 +1640,11 @@
   for (size_t i = 0; i < kActionsNeedingWindowLength; ++i) {
     window.reset(CreateTestWindowInShellWithBounds(gfx::Rect(5, 5, 20, 20)));
     wm::ActivateWindow(window.get());
-    controller->TriggerAccessibilityAlert(mojom::AccessibilityAlert::NONE);
-    controller->FlushMojoForTest();
-    GetController()->PerformActionIfEnabled(kActionsNeedingWindow[i], {});
-    controller->FlushMojoForTest();
+    accessibility_controller->TriggerAccessibilityAlert(
+        mojom::AccessibilityAlert::NONE);
+    accessibility_controller->FlushMojoForTest();
+    controller_->PerformActionIfEnabled(kActionsNeedingWindow[i], {});
+    accessibility_controller->FlushMojoForTest();
     EXPECT_NE(mojom::AccessibilityAlert::WINDOW_NEEDED,
               client.last_a11y_alert());
   }
@@ -1592,11 +1653,12 @@
   for (size_t i = 0; i < kActionsNeedingWindowLength; ++i) {
     window.reset(CreateTestWindowInShellWithBounds(gfx::Rect(5, 5, 20, 20)));
     wm::ActivateWindow(window.get());
-    GetController()->PerformActionIfEnabled(WINDOW_MINIMIZE, {});
-    controller->TriggerAccessibilityAlert(mojom::AccessibilityAlert::NONE);
-    controller->FlushMojoForTest();
-    GetController()->PerformActionIfEnabled(kActionsNeedingWindow[i], {});
-    controller->FlushMojoForTest();
+    controller_->PerformActionIfEnabled(WINDOW_MINIMIZE, {});
+    accessibility_controller->TriggerAccessibilityAlert(
+        mojom::AccessibilityAlert::NONE);
+    accessibility_controller->FlushMojoForTest();
+    controller_->PerformActionIfEnabled(kActionsNeedingWindow[i], {});
+    accessibility_controller->FlushMojoForTest();
     EXPECT_NE(mojom::AccessibilityAlert::WINDOW_NEEDED,
               client.last_a11y_alert());
   }
@@ -1694,9 +1756,9 @@
   for (size_t i = 0; i < kDeprecatedAcceleratorsLength; ++i) {
     const AcceleratorData& entry = kDeprecatedAccelerators[i];
 
-    auto itr = GetController()->actions_with_deprecations_.find(entry.action);
-    ASSERT_TRUE(itr != GetController()->actions_with_deprecations_.end());
-    const DeprecatedAcceleratorData* data = itr->second;
+    const DeprecatedAcceleratorData* data =
+        test_api_->GetDeprecatedAcceleratorData(entry.action);
+    DCHECK(data);
 
     EXPECT_TRUE(IsMessageCenterEmpty());
     ui::Accelerator deprecated_accelerator = CreateAccelerator(entry);
diff --git a/ash/accessibility/accessibility_controller.cc b/ash/accessibility/accessibility_controller.cc
index d5270518..431cc06 100644
--- a/ash/accessibility/accessibility_controller.cc
+++ b/ash/accessibility/accessibility_controller.cc
@@ -724,6 +724,11 @@
   accessibility_highlight_controller_->SetCaretBounds(bounds_in_screen);
 }
 
+void AccessibilityController::SetAccessibilityPanelAlwaysVisible(
+    bool always_visible) {
+  GetLayoutManager()->SetAlwaysVisible(always_visible);
+}
+
 void AccessibilityController::SetAccessibilityPanelBounds(
     const gfx::Rect& bounds,
     mojom::AccessibilityPanelState state) {
diff --git a/ash/accessibility/accessibility_controller.h b/ash/accessibility/accessibility_controller.h
index 31c2df7..87eccb7 100644
--- a/ash/accessibility/accessibility_controller.h
+++ b/ash/accessibility/accessibility_controller.h
@@ -177,6 +177,7 @@
   void BrailleDisplayStateChanged(bool connected) override;
   void SetFocusHighlightRect(const gfx::Rect& bounds_in_screen) override;
   void SetCaretBounds(const gfx::Rect& bounds_in_screen) override;
+  void SetAccessibilityPanelAlwaysVisible(bool always_visible) override;
   void SetAccessibilityPanelBounds(
       const gfx::Rect& bounds,
       mojom::AccessibilityPanelState state) override;
diff --git a/ash/accessibility/accessibility_panel_layout_manager.cc b/ash/accessibility/accessibility_panel_layout_manager.cc
index 3f48eaf..890706e 100644
--- a/ash/accessibility/accessibility_panel_layout_manager.cc
+++ b/ash/accessibility/accessibility_panel_layout_manager.cc
@@ -27,6 +27,11 @@
   display::Screen::GetScreen()->RemoveObserver(this);
 }
 
+void AccessibilityPanelLayoutManager::SetAlwaysVisible(bool always_visible) {
+  always_visible_ = always_visible;
+  UpdateWindowBounds();
+}
+
 void AccessibilityPanelLayoutManager::SetPanelBounds(
     const gfx::Rect& bounds,
     mojom::AccessibilityPanelState state) {
@@ -99,6 +104,12 @@
   RootWindowController* root_controller =
       RootWindowController::ForWindow(root_window);
 
+  aura::Window* current = panel_window_;
+  while (current->parent()) {
+    current->parent()->StackChildAtTop(current);
+    current = current->parent();
+  }
+
   gfx::Rect bounds = panel_bounds_;
 
   // The panel can make itself fill the screen (including covering the shelf).
@@ -116,6 +127,13 @@
       bounds.set_height(0);
   }
 
+  // If a fullscreen browser window is open, give the panel a height of 0
+  // unless it's active or always_visible_ is true.
+  if (!always_visible_ && root_controller->GetWindowForFullscreenMode() &&
+      !::wm::IsActiveWindow(panel_window_)) {
+    bounds.set_height(0);
+  }
+
   // Make sure the accessibility panel is always below the Docked Magnifier
   // viewport so it shows up and gets magnified.
   int magnifier_height =
diff --git a/ash/accessibility/accessibility_panel_layout_manager.h b/ash/accessibility/accessibility_panel_layout_manager.h
index cd170f9..70c90c2a 100644
--- a/ash/accessibility/accessibility_panel_layout_manager.h
+++ b/ash/accessibility/accessibility_panel_layout_manager.h
@@ -38,6 +38,7 @@
   ~AccessibilityPanelLayoutManager() override;
 
   // Controls the panel's visibility and location.
+  void SetAlwaysVisible(bool always_visible);
   void SetPanelBounds(const gfx::Rect& bounds,
                       mojom::AccessibilityPanelState state);
 
@@ -81,6 +82,9 @@
   // Window bounds when not in fullscreen
   gfx::Rect panel_bounds_ = gfx::Rect(0, 0, 0, 0);
 
+  // Determines whether panel is hidden when browser is in fullscreen.
+  bool always_visible_ = false;
+
   // Determines how the panel_bounds_ are used when displaying the panel.
   mojom::AccessibilityPanelState panel_state_ =
       mojom::AccessibilityPanelState::BOUNDED;
diff --git a/ash/app_list/app_list_metrics_unittest.cc b/ash/app_list/app_list_metrics_unittest.cc
index ba447e9..519b9af5 100644
--- a/ash/app_list/app_list_metrics_unittest.cc
+++ b/ash/app_list/app_list_metrics_unittest.cc
@@ -21,7 +21,6 @@
 #include "ash/app_list/views/suggestion_chip_container_view.h"
 #include "ash/public/cpp/shelf_item_delegate.h"
 #include "ash/public/cpp/shelf_model.h"
-#include "ash/public/interfaces/app_list.mojom.h"
 #include "ash/public/interfaces/app_list_view.mojom.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_view.h"
@@ -207,8 +206,7 @@
   GetAppListTestHelper()->WaitUntilIdle();
 
   histogram_tester.ExpectBucketCount(
-      "Apps.AppListAppLaunchedV2.Half",
-      mojom::AppListLaunchedFrom::kLaunchedFromShelf,
+      "Apps.AppListAppLaunchedV2.Half", AppListLaunchedFrom::kLaunchedFromShelf,
       1 /* Number of times launched from shelf */);
 }
 
@@ -228,7 +226,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.Half",
-      mojom::AppListLaunchedFrom::kLaunchedFromSearchBox,
+      AppListLaunchedFrom::kLaunchedFromSearchBox,
       1 /* Number of times launched from search box */);
 }
 
@@ -253,7 +251,7 @@
   GetAppListTestHelper()->WaitUntilIdle();
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.FullscreenSearch",
-      mojom::AppListLaunchedFrom::kLaunchedFromSearchBox,
+      AppListLaunchedFrom::kLaunchedFromSearchBox,
       1 /* Number of times launched from search box */);
 }
 
@@ -277,7 +275,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.FullscreenSearch",
-      mojom::AppListLaunchedFrom::kLaunchedFromShelf,
+      AppListLaunchedFrom::kLaunchedFromShelf,
       1 /* Number of times launched from shelf */);
 }
 
@@ -296,7 +294,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.FullscreenAllApps",
-      mojom::AppListLaunchedFrom::kLaunchedFromSuggestionChip,
+      AppListLaunchedFrom::kLaunchedFromSuggestionChip,
       1 /* Number of times launched from chip */);
 }
 
@@ -315,7 +313,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.FullscreenAllApps",
-      mojom::AppListLaunchedFrom::kLaunchedFromGrid,
+      AppListLaunchedFrom::kLaunchedFromGrid,
       1 /* Number of times launched from grid */);
 }
 
@@ -334,7 +332,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.FullscreenAllApps",
-      mojom::AppListLaunchedFrom::kLaunchedFromShelf,
+      AppListLaunchedFrom::kLaunchedFromShelf,
       1 /* Number of times launched from shelf */);
 }
 
@@ -350,7 +348,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.Peeking",
-      mojom::AppListLaunchedFrom::kLaunchedFromShelf,
+      AppListLaunchedFrom::kLaunchedFromShelf,
       1 /* Number of times launched from shelf */);
 }
 
@@ -366,7 +364,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.Peeking",
-      mojom::AppListLaunchedFrom::kLaunchedFromSuggestionChip,
+      AppListLaunchedFrom::kLaunchedFromSuggestionChip,
       1 /* Number of times launched from chip */);
 }
 
@@ -381,7 +379,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.Closed",
-      mojom::AppListLaunchedFrom::kLaunchedFromShelf,
+      AppListLaunchedFrom::kLaunchedFromShelf,
       1 /* Number of times launched from shelf */);
 
   // Open the launcher to peeking.
@@ -396,7 +394,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.Closed",
-      mojom::AppListLaunchedFrom::kLaunchedFromShelf,
+      AppListLaunchedFrom::kLaunchedFromShelf,
       2 /* Number of times launched from shelf */);
 }
 
@@ -413,7 +411,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.HomecherAllApps",
-      mojom::AppListLaunchedFrom::kLaunchedFromShelf,
+      AppListLaunchedFrom::kLaunchedFromShelf,
       1 /* Number of times launched from shelf */);
 }
 
@@ -431,7 +429,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.HomecherAllApps",
-      mojom::AppListLaunchedFrom::kLaunchedFromGrid,
+      AppListLaunchedFrom::kLaunchedFromGrid,
       1 /* Number of times launched from grid */);
 }
 
@@ -450,7 +448,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.HomecherAllApps",
-      mojom::AppListLaunchedFrom::kLaunchedFromSuggestionChip,
+      AppListLaunchedFrom::kLaunchedFromSuggestionChip,
       1 /* Number of times launched from chip */);
 }
 
@@ -473,7 +471,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.HomecherSearch",
-      mojom::AppListLaunchedFrom::kLaunchedFromShelf,
+      AppListLaunchedFrom::kLaunchedFromShelf,
       1 /* Number of times launched from shelf */);
 }
 
@@ -497,7 +495,7 @@
 
   histogram_tester.ExpectBucketCount(
       "Apps.AppListAppLaunchedV2.HomecherSearch",
-      mojom::AppListLaunchedFrom::kLaunchedFromSearchBox,
+      AppListLaunchedFrom::kLaunchedFromSearchBox,
       1 /* Number of times launched from search box */);
 }
 
diff --git a/ash/app_list/views/app_list_item_view.cc b/ash/app_list/views/app_list_item_view.cc
index ef8bc0b..228190e 100644
--- a/ash/app_list/views/app_list_item_view.cc
+++ b/ash/app_list/views/app_list_item_view.cc
@@ -784,6 +784,10 @@
 }
 
 void AppListItemView::OnMenuClosed() {
+  // Release menu since its menu model delegate (AppContextMenu) could be
+  // released as a result of menu command execution.
+  context_menu_.reset();
+
   if (!menu_close_initiated_from_drag_) {
     // If the menu was not closed due to a drag sequence(e.g. multi touch) reset
     // the drag state.
diff --git a/ash/app_list/views/apps_grid_view.cc b/ash/app_list/views/apps_grid_view.cc
index 6eff474..b0ba5aa 100644
--- a/ash/app_list/views/apps_grid_view.cc
+++ b/ash/app_list/views/apps_grid_view.cc
@@ -1932,8 +1932,6 @@
   AnnounceReorder(target_index);
   ReparentItemForReorder(reparented_view_in_root_grid, target_index);
 
-  contents_view_->GetAppsContainerView()->ResetForShowApps();
-
   GetViewAtIndex(target_index)->RequestFocus();
   Layout();
   RecordAppMovingTypeMetrics(kMoveByKeyboardOutOfFolder);
diff --git a/ash/app_list/views/search_result_tile_item_view.cc b/ash/app_list/views/search_result_tile_item_view.cc
index c4c6f9d..5d978a07 100644
--- a/ash/app_list/views/search_result_tile_item_view.cc
+++ b/ash/app_list/views/search_result_tile_item_view.cc
@@ -382,6 +382,9 @@
 }
 
 void SearchResultTileItemView::OnMenuClosed() {
+  // Release menu since its menu model delegate (AppContextMenu) could be
+  // released as a result of menu command execution.
+  context_menu_.reset();
   OnBlur();
 }
 
diff --git a/ash/app_list/views/search_result_view.cc b/ash/app_list/views/search_result_view.cc
index 8fe2b9b..119456f 100644
--- a/ash/app_list/views/search_result_view.cc
+++ b/ash/app_list/views/search_result_view.cc
@@ -436,6 +436,12 @@
   return IsMouseHovered() || selected();
 }
 
+void SearchResultView::OnMenuClosed() {
+  // Release menu since its menu model delegate (AppContextMenu) could be
+  // released as a result of menu command execution.
+  context_menu_.reset();
+}
+
 void SearchResultView::ShowContextMenuForViewImpl(
     views::View* source,
     const gfx::Point& point,
@@ -455,7 +461,7 @@
     const gfx::Point& point,
     ui::MenuSourceType source_type,
     std::unique_ptr<ui::SimpleMenuModel> menu_model) {
-  if (!menu_model || context_menu_->IsShowingMenu())
+  if (!menu_model || (context_menu_ && context_menu_->IsShowingMenu()))
     return;
 
   AppLaunchedMetricParams metric_params = {
@@ -466,7 +472,9 @@
   context_menu_ = std::make_unique<AppListMenuModelAdapter>(
       std::string(), std::move(menu_model), GetWidget(), source_type,
       metric_params, AppListMenuModelAdapter::SEARCH_RESULT,
-      base::OnceClosure(), view_delegate_->GetSearchModel()->tablet_mode());
+      base::BindOnce(&SearchResultView::OnMenuClosed,
+                     weak_ptr_factory_.GetWeakPtr()),
+      view_delegate_->GetSearchModel()->tablet_mode());
   context_menu_->Run(gfx::Rect(point, gfx::Size()),
                      views::MenuAnchorPosition::kTopLeft,
                      views::MenuRunner::HAS_MNEMONICS);
diff --git a/ash/app_list/views/search_result_view.h b/ash/app_list/views/search_result_view.h
index 48bacf7..aee30bd 100644
--- a/ash/app_list/views/search_result_view.h
+++ b/ash/app_list/views/search_result_view.h
@@ -113,6 +113,9 @@
   void OnSearchResultActionActivated(size_t index, int event_flags) override;
   bool IsSearchResultHoveredOrSelected() override;
 
+  // Invoked when the context menu closes.
+  void OnMenuClosed();
+
   // Parent list view. Owned by views hierarchy.
   SearchResultListView* list_view_;
 
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index abbf7f8..62d9a107 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -865,21 +865,26 @@
       <message name="IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING_SUBLABEL" desc="The sub label text used when network is activating. The network name is shown above this label.">
         Activating
       </message>
+
       <message name="IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECTED_TOOLTIP" desc="The tooltip text used when network is not connected.">
         Disconnected
       </message>
       <message name="IDS_ASH_STATUS_TRAY_NETWORK_NOT_CONNECTED_A11Y" desc="The message used by accessibility for telling that currently the device is not connected to a network.">
         Not connected to network
       </message>
-      <message name="IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING_TOOLTIP" desc="The tooltip text used when network is connecting.">
-        Connecting to <ph name="NETWORK_NAME">$1<ex>public wifi</ex></ph>
+      <message name="IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING" desc="Message for the network tray tooltip and a11y name when connecting to a network.">
+        Connecting to <ph name="NAME">$1<ex>GoogleGuest</ex></ph>
       </message>
-      <message name="IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED_TOOLTIP" desc="The tooltip text used when network is connected.">
-        Connected to <ph name="NETWORK_NAME">$1<ex>public wifi</ex></ph>
+      <message name="IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED" desc="Message for the network tray tooltip and a11y name when connected to a network.">
+        Connected to <ph name="NAME">$1<ex>GoogleGuest</ex></ph>
       </message>
-      <message name="IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING_TOOLTIP" desc="The tooltip text used when network is activating.">
-        Activating <ph name="NETWORK_NAME">$1<ex>cellular network</ex></ph>
+      <message name="IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED_TOOLTIP" desc="The tooltip text used when connected to a network.">
+        Connected to <ph name="NAME">$1<ex>GoogleGuest</ex></ph>, <ph name="STRENGTH">$2<ex>Weak signal.</ex></ph>
       </message>
+      <message name="IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING" desc="Message for the network tray tooltip and a11y name when activating a network.">
+        Activating <ph name="NAME">$1<ex>YBH Cellular</ex></ph>
+      </message>
+
       <message name="IDS_ASH_STATUS_TRAY_NETWORK_TOGGLE_TOOLTIP" desc="The tooltip text for the button that toggles network enabled/disabled state.">
         Toggle network connection. <ph name="STATE_TEXT">$1<ex>Connected to public wifi</ex></ph>
       </message>
@@ -1218,18 +1223,6 @@
       <message name="IDS_ASH_STATUS_TRAY_SIM_CARD_LOCKED" desc="Message for the status area when a SIM card is locked.">
         SIM card is locked
       </message>
-      <message name="IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING" desc="Message for the network tray tooltip and network list when activating a network.">
-        Activating <ph name="NAME">$1<ex>YBH Cellular</ex></ph>
-      </message>
-      <message name="IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED" desc="Message for the network tray tooltip and network list when connected to a network.">
-        Connected to <ph name="NAME">$1<ex>GoogleGuest</ex></ph>
-      </message>
-      <message name="IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED_ACCESSIBLE" desc="The accessible message for when connected to a network.">
-        Connected to <ph name="NAME">$1<ex>GoogleGuest</ex></ph>, <ph name="STRENGTH">$2<ex>Weak signal.</ex></ph>
-      </message>
-      <message name="IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING" desc="Message for the network tray tooltip and network list when connecting to a network.">
-        Connecting to <ph name="NAME">$1<ex>GoogleGuest</ex></ph>
-      </message>
       <message name="IDS_ASH_STATUS_TRAY_NETWORK_LIST_ACTIVATE" desc="Message for the network list to activate the network.">
         Activate <ph name="NETWORKSERVICE">$1<ex>YBH Cellular</ex></ph>
       </message>
diff --git a/ash/components/fast_ink/OWNERS b/ash/components/fast_ink/OWNERS
index 1876d6b2..872aca7 100644
--- a/ash/components/fast_ink/OWNERS
+++ b/ash/components/fast_ink/OWNERS
@@ -1,2 +1,3 @@
 reveman@chromium.org
 kaznacheev@chromium.org
+malaykeshav@chromium.org
diff --git a/ash/components/strings/ash_components_strings_zh-TW.xtb b/ash/components/strings/ash_components_strings_zh-TW.xtb
index 7ae2e91..0069f04 100644
--- a/ash/components/strings/ash_components_strings_zh-TW.xtb
+++ b/ash/components/strings/ash_components_strings_zh-TW.xtb
@@ -149,7 +149,7 @@
 <translation id="6755851152783057058">切換至最近一次使用的輸入法</translation>
 <translation id="6760706756348334449">調低音量</translation>
 <translation id="6941333068993625698">提供意見</translation>
-<translation id="6981982820502123353">協助工具</translation>
+<translation id="6981982820502123353">無障礙設定</translation>
 <translation id="7020813747703216897">找不到相符的搜尋結果</translation>
 <translation id="7025325401470358758">下一個窗格</translation>
 <translation id="7076878155205969899">關閉音效</translation>
diff --git a/ash/ime/ime_engine_factory_registry.cc b/ash/ime/ime_engine_factory_registry.cc
deleted file mode 100644
index e7fac77..0000000
--- a/ash/ime/ime_engine_factory_registry.cc
+++ /dev/null
@@ -1,40 +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/ime/ime_engine_factory_registry.h"
-
-#include <utility>
-
-namespace ash {
-
-ImeEngineFactoryRegistry::ImeEngineFactoryRegistry() = default;
-
-ImeEngineFactoryRegistry::~ImeEngineFactoryRegistry() = default;
-
-void ImeEngineFactoryRegistry::BindRequest(
-    ime::mojom::ImeEngineFactoryRegistryRequest request) {
-  registry_bindings_.AddBinding(this, std::move(request));
-}
-
-void ImeEngineFactoryRegistry::ConnectToImeEngine(
-    ime::mojom::ImeEngineRequest engine_request,
-    ime::mojom::ImeEngineClientPtr client) {
-  if (engine_factory_) {
-    engine_factory_->CreateEngine(std::move(engine_request), std::move(client));
-  } else {
-    pending_engine_request_ = std::move(engine_request);
-    pending_engine_client_ = std::move(client);
-  }
-}
-
-void ImeEngineFactoryRegistry::ActivateFactory(
-    ime::mojom::ImeEngineFactoryPtr ief) {
-  engine_factory_ = std::move(ief);
-  if (pending_engine_request_) {
-    engine_factory_->CreateEngine(std::move(pending_engine_request_),
-                                  std::move(pending_engine_client_));
-  }
-}
-
-}  // namespace ash
diff --git a/ash/ime/ime_engine_factory_registry.h b/ash/ime/ime_engine_factory_registry.h
deleted file mode 100644
index b49fac0..0000000
--- a/ash/ime/ime_engine_factory_registry.h
+++ /dev/null
@@ -1,49 +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_IME_IME_ENGINE_FACTORY_REGISTRY_H_
-#define ASH_IME_IME_ENGINE_FACTORY_REGISTRY_H_
-
-#include <memory>
-
-#include "ash/ash_export.h"
-#include "mojo/public/cpp/bindings/binding_set.h"
-#include "ui/base/ime/mojo/ime.mojom.h"
-#include "ui/base/ime/mojo/ime_engine_factory_registry.mojom.h"
-
-namespace ash {
-
-// This class maintains the active input method engine, as well as forwarding
-// requests from clients to the active engine. In particular, engines connect
-// using ime::mojom::ImeEngineFactoryRegistry and clients connect using
-// ime::mojom::ImeEngineFactory.
-class ASH_EXPORT ImeEngineFactoryRegistry
-    : public ime::mojom::ImeEngineFactoryRegistry {
- public:
-  ImeEngineFactoryRegistry();
-  ~ImeEngineFactoryRegistry() override;
-
-  void BindRequest(ime::mojom::ImeEngineFactoryRegistryRequest request);
-
-  void ConnectToImeEngine(ime::mojom::ImeEngineRequest engine_request,
-                          ime::mojom::ImeEngineClientPtr client);
-
-  bool HasPendingRequestForTesting() const { return !!pending_engine_request_; }
-
- private:
-  // ime::mojom::ImeEngineFactoryRegistry:
-  void ActivateFactory(ime::mojom::ImeEngineFactoryPtr ief) override;
-
-  mojo::BindingSet<ime::mojom::ImeEngineFactoryRegistry> registry_bindings_;
-
-  ime::mojom::ImeEngineFactoryPtr engine_factory_;
-  ime::mojom::ImeEngineRequest pending_engine_request_;
-  ime::mojom::ImeEngineClientPtr pending_engine_client_;
-
-  DISALLOW_COPY_AND_ASSIGN(ImeEngineFactoryRegistry);
-};
-
-}  // namespace ash
-
-#endif  // ASH_IME_IME_ENGINE_FACTORY_REGISTRY_H_
diff --git a/ash/ime/ime_engine_factory_registry_unittest.cc b/ash/ime/ime_engine_factory_registry_unittest.cc
deleted file mode 100644
index c2b9279..0000000
--- a/ash/ime/ime_engine_factory_registry_unittest.cc
+++ /dev/null
@@ -1,121 +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/ime/ime_engine_factory_registry.h"
-
-#include <memory>
-
-#include "ash/shell.h"
-#include "ash/test/ash_test_base.h"
-#include "base/run_loop.h"
-#include "mojo/public/cpp/bindings/binding.h"
-
-namespace ash {
-namespace {
-
-class TestImeEngineClient : public ime::mojom::ImeEngineClient {
- public:
-  TestImeEngineClient() : binding_(this) {}
-  ~TestImeEngineClient() override = default;
-
-  ime::mojom::ImeEngineClientPtr BindInterface() {
-    ime::mojom::ImeEngineClientPtr ptr;
-    binding_.Bind(mojo::MakeRequest(&ptr));
-    return ptr;
-  }
-
- private:
-  // ime::mojom::ImeEngineClient:
-  void CommitText(const std::string& text) override {}
-  void UpdateCompositionText(const ui::CompositionText& composition,
-                             uint32_t cursor_pos,
-                             bool visible) override {}
-  void DeleteSurroundingText(int32_t offset, uint32_t length) override {}
-  void SendKeyEvent(std::unique_ptr<ui::Event> key_event) override {}
-  void Reconnect() override {}
-
-  mojo::Binding<ime::mojom::ImeEngineClient> binding_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestImeEngineClient);
-};
-
-class TestImeEngineFactory : public ime::mojom::ImeEngineFactory {
- public:
-  TestImeEngineFactory() : binding_(this) {
-    run_loop_ = std::make_unique<base::RunLoop>();
-  }
-  ~TestImeEngineFactory() override = default;
-
-  ime::mojom::ImeEngineFactoryPtr BindInterface() {
-    ime::mojom::ImeEngineFactoryPtr ptr;
-    binding_.Bind(mojo::MakeRequest(&ptr));
-    return ptr;
-  }
-
-  void WaitForEngineCreated() { run_loop_->Run(); }
-  bool engine_created() const { return engine_created_; }
-
- private:
-  // ime::mojom::ImeEngineFactory:
-  void CreateEngine(ime::mojom::ImeEngineRequest engine_request,
-                    ime::mojom::ImeEngineClientPtr client) override {
-    engine_created_ = true;
-    run_loop_->Quit();
-  }
-
-  mojo::Binding<ime::mojom::ImeEngineFactory> binding_;
-  std::unique_ptr<base::RunLoop> run_loop_;
-  bool engine_created_ = false;
-
-  DISALLOW_COPY_AND_ASSIGN(TestImeEngineFactory);
-};
-
-class ImeEngineFactoryRegistryTest : public AshTestBase {
- public:
-  ImeEngineFactoryRegistryTest() {}
-  ~ImeEngineFactoryRegistryTest() override = default;
-
-  void SetUp() override {
-    AshTestBase::SetUp();
-
-    engine_factory_ = std::make_unique<TestImeEngineFactory>();
-    Shell::Get()->ime_engine_factory_registry()->BindRequest(
-        mojo::MakeRequest(&registry_ptr_));
-  }
-
-  void TearDown() override {
-    registry_ptr_.reset();
-    engine_factory_.reset();
-
-    AshTestBase::TearDown();
-  }
-
- protected:
-  std::unique_ptr<TestImeEngineFactory> engine_factory_;
-  TestImeEngineClient engine_client_;
-  ime::mojom::ImeEngineFactoryRegistryPtr registry_ptr_;
-
-  DISALLOW_COPY_AND_ASSIGN(ImeEngineFactoryRegistryTest);
-};
-
-TEST_F(ImeEngineFactoryRegistryTest, ConnectBeforeActivateFactory) {
-  ime::mojom::ImeEnginePtr engine_ptr;
-  Shell::Get()->ime_engine_factory_registry()->ConnectToImeEngine(
-      mojo::MakeRequest(&engine_ptr), engine_client_.BindInterface());
-  registry_ptr_->ActivateFactory(engine_factory_->BindInterface());
-  engine_factory_->WaitForEngineCreated();
-  ASSERT_TRUE(engine_factory_->engine_created());
-}
-
-TEST_F(ImeEngineFactoryRegistryTest, ConnectAfterActivateFactory) {
-  registry_ptr_->ActivateFactory(engine_factory_->BindInterface());
-  ime::mojom::ImeEnginePtr engine_ptr;
-  Shell::Get()->ime_engine_factory_registry()->ConnectToImeEngine(
-      mojo::MakeRequest(&engine_ptr), engine_client_.BindInterface());
-  engine_factory_->WaitForEngineCreated();
-  ASSERT_TRUE(engine_factory_->engine_created());
-}
-
-}  // namespace
-}  // namespace ash
diff --git a/ash/media/media_notification_container.h b/ash/media/media_notification_container.h
new file mode 100644
index 0000000..cf8abca
--- /dev/null
+++ b/ash/media/media_notification_container.h
@@ -0,0 +1,23 @@
+// 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_MEDIA_MEDIA_NOTIFICATION_CONTAINER_H_
+#define ASH_MEDIA_MEDIA_NOTIFICATION_CONTAINER_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+
+// MediaNotificationContainer is an interface for containers of
+// MediaNotificationView components to receive events from the
+// MediaNotificationView.
+class ASH_EXPORT MediaNotificationContainer {
+ public:
+  // Called when MediaNotificationView's expanded state changes.
+  virtual void OnExpanded(bool expanded) = 0;
+};
+
+}  // namespace ash
+
+#endif  // ASH_MEDIA_MEDIA_NOTIFICATION_CONTAINER_H_
diff --git a/ash/media/media_notification_container_impl.cc b/ash/media/media_notification_container_impl.cc
new file mode 100644
index 0000000..b0f9979
--- /dev/null
+++ b/ash/media/media_notification_container_impl.cc
@@ -0,0 +1,96 @@
+// 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/media/media_notification_container_impl.h"
+
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/views/notification_control_buttons_view.h"
+#include "ui/native_theme/native_theme_dark_aura.h"
+#include "ui/views/background.h"
+#include "ui/views/layout/fill_layout.h"
+
+namespace ash {
+
+MediaNotificationContainerImpl::MediaNotificationContainerImpl(
+    const message_center::Notification& notification,
+    base::WeakPtr<MediaNotificationItem> item)
+    : message_center::MessageView(notification),
+      control_buttons_view_(
+          std::make_unique<message_center::NotificationControlButtonsView>(
+              this)),
+      view_(this,
+            std::move(item),
+            control_buttons_view_.get(),
+            message_center::MessageCenter::Get()
+                ->GetSystemNotificationAppName()) {
+  SetLayoutManager(std::make_unique<views::FillLayout>());
+
+  SetNativeTheme(ui::NativeThemeDarkAura::instance());
+
+  // Since we own these, we don't want Views to destroy them when their parent
+  // is destroyed.
+  control_buttons_view_->set_owned_by_client();
+  view_.set_owned_by_client();
+
+  AddChildView(&view_);
+
+  SetBackground(views::CreateSolidBackground(SK_ColorTRANSPARENT));
+}
+
+MediaNotificationContainerImpl::~MediaNotificationContainerImpl() = default;
+
+void MediaNotificationContainerImpl::UpdateWithNotification(
+    const message_center::Notification& notification) {
+  MessageView::UpdateWithNotification(notification);
+
+  UpdateControlButtonsVisibilityWithNotification(notification);
+
+  PreferredSizeChanged();
+  Layout();
+  SchedulePaint();
+}
+
+message_center::NotificationControlButtonsView*
+MediaNotificationContainerImpl::GetControlButtonsView() const {
+  return control_buttons_view_.get();
+}
+
+void MediaNotificationContainerImpl::SetExpanded(bool expanded) {
+  view_.SetExpanded(expanded);
+}
+
+void MediaNotificationContainerImpl::UpdateCornerRadius(int top_radius,
+                                                        int bottom_radius) {
+  view_.UpdateCornerRadius(top_radius, bottom_radius);
+}
+
+void MediaNotificationContainerImpl::OnExpanded(bool expanded) {
+  PreferredSizeChanged();
+}
+
+void MediaNotificationContainerImpl::OnMouseEvent(ui::MouseEvent* event) {
+  switch (event->type()) {
+    case ui::ET_MOUSE_ENTERED:
+    case ui::ET_MOUSE_EXITED:
+      UpdateControlButtonsVisibility();
+      break;
+    default:
+      break;
+  }
+
+  View::OnMouseEvent(event);
+}
+
+void MediaNotificationContainerImpl::
+    UpdateControlButtonsVisibilityWithNotification(
+        const message_center::Notification& notification) {
+  // Media notifications do not use the settings and snooze buttons.
+  DCHECK(!notification.should_show_settings_button());
+  DCHECK(!notification.should_show_snooze_button());
+
+  control_buttons_view_->ShowCloseButton(!notification.pinned());
+  UpdateControlButtonsVisibility();
+}
+
+}  // namespace ash
diff --git a/ash/media/media_notification_container_impl.h b/ash/media/media_notification_container_impl.h
new file mode 100644
index 0000000..5aed317
--- /dev/null
+++ b/ash/media/media_notification_container_impl.h
@@ -0,0 +1,59 @@
+// 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_MEDIA_MEDIA_NOTIFICATION_CONTAINER_IMPL_H_
+#define ASH_MEDIA_MEDIA_NOTIFICATION_CONTAINER_IMPL_H_
+
+#include "ash/ash_export.h"
+#include "ash/media/media_notification_container.h"
+#include "ash/media/media_notification_view.h"
+#include "ui/message_center/views/message_view.h"
+
+namespace ash {
+
+class MediaNotificationItem;
+
+// MediaNotificationContainerImpl will show up as a custom notification. It will
+// show the currently playing media and provide playback controls. There will
+// also be control buttons (e.g. close) in the top right corner that will hide
+// and show if the notification is hovered.
+class ASH_EXPORT MediaNotificationContainerImpl
+    : public message_center::MessageView,
+      public MediaNotificationContainer {
+ public:
+  explicit MediaNotificationContainerImpl(
+      const message_center::Notification& notification,
+      base::WeakPtr<MediaNotificationItem> item);
+  ~MediaNotificationContainerImpl() override;
+
+  // message_center::MessageView:
+  void UpdateWithNotification(
+      const message_center::Notification& notification) override;
+  message_center::NotificationControlButtonsView* GetControlButtonsView()
+      const override;
+  void SetExpanded(bool expanded) override;
+  void UpdateCornerRadius(int top_radius, int bottom_radius) override;
+
+  // MediaNotificationContainer:
+  void OnExpanded(bool expanded) override;
+
+  // views::View:
+  void OnMouseEvent(ui::MouseEvent* event) override;
+
+ private:
+  void UpdateControlButtonsVisibilityWithNotification(
+      const message_center::Notification& notification);
+
+  // View containing close and settings buttons.
+  std::unique_ptr<message_center::NotificationControlButtonsView>
+      control_buttons_view_;
+
+  MediaNotificationView view_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaNotificationContainerImpl);
+};
+
+}  // namespace ash
+
+#endif  // ASH_MEDIA_MEDIA_NOTIFICATION_CONTAINER_IMPL_H_
diff --git a/ash/media/media_notification_controller_impl.cc b/ash/media/media_notification_controller_impl.cc
index 14a27af..a15dc77a 100644
--- a/ash/media/media_notification_controller_impl.cc
+++ b/ash/media/media_notification_controller_impl.cc
@@ -5,8 +5,8 @@
 #include "ash/media/media_notification_controller_impl.h"
 
 #include "ash/media/media_notification_constants.h"
+#include "ash/media/media_notification_container_impl.h"
 #include "ash/media/media_notification_item.h"
-#include "ash/media/media_notification_view.h"
 #include "ash/public/cpp/notification_utils.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/session/session_observer.h"
@@ -193,7 +193,7 @@
   message_center::MessageCenter::Get()->RemoveNotification(id, false);
 }
 
-std::unique_ptr<MediaNotificationView>
+std::unique_ptr<MediaNotificationContainerImpl>
 MediaNotificationControllerImpl::CreateMediaNotification(
     const message_center::Notification& notification) {
   base::WeakPtr<MediaNotificationItem> item;
@@ -202,7 +202,8 @@
   if (it != notifications_.end())
     item = it->second.GetWeakPtr();
 
-  return std::make_unique<MediaNotificationView>(notification, std::move(item));
+  return std::make_unique<MediaNotificationContainerImpl>(notification,
+                                                          std::move(item));
 }
 
 void MediaNotificationControllerImpl::RecordConcurrentNotificationCount() {
diff --git a/ash/media/media_notification_controller_impl.h b/ash/media/media_notification_controller_impl.h
index 85a3d84..24b8645 100644
--- a/ash/media/media_notification_controller_impl.h
+++ b/ash/media/media_notification_controller_impl.h
@@ -31,7 +31,7 @@
 class MediaNotificationBlocker;
 }  // namespace
 
-class MediaNotificationView;
+class MediaNotificationContainerImpl;
 
 // MediaNotificationControllerImpl will show/hide media notifications when media
 // sessions are active. These notifications will show metadata and playback
@@ -58,7 +58,7 @@
   void ShowNotification(const std::string& id) override;
   void HideNotification(const std::string& id) override;
 
-  std::unique_ptr<MediaNotificationView> CreateMediaNotification(
+  std::unique_ptr<MediaNotificationContainerImpl> CreateMediaNotification(
       const message_center::Notification& notification);
 
   ash::MediaNotificationItem* GetItem(const std::string& id) {
diff --git a/ash/media/media_notification_view.cc b/ash/media/media_notification_view.cc
index e43b827..7661d2f 100644
--- a/ash/media/media_notification_view.cc
+++ b/ash/media/media_notification_view.cc
@@ -6,8 +6,8 @@
 
 #include "ash/media/media_notification_background.h"
 #include "ash/media/media_notification_constants.h"
+#include "ash/media/media_notification_container.h"
 #include "ash/media/media_notification_item.h"
-#include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/stl_util.h"
@@ -18,11 +18,8 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/font.h"
 #include "ui/gfx/font_list.h"
-#include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/message_center_constants.h"
-#include "ui/message_center/views/notification_control_buttons_view.h"
 #include "ui/message_center/views/notification_header_view.h"
-#include "ui/native_theme/native_theme_dark_aura.h"
 #include "ui/views/controls/button/image_button_factory.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/style/typography.h"
@@ -108,24 +105,26 @@
     "Media.Notification.MetadataPresent";
 
 MediaNotificationView::MediaNotificationView(
-    const message_center::Notification& notification,
-    base::WeakPtr<MediaNotificationItem> item)
-    : message_center::MessageView(notification), item_(std::move(item)) {
+    MediaNotificationContainer* container,
+    base::WeakPtr<MediaNotificationItem> item,
+    views::View* header_row_controls_view,
+    const base::string16& default_app_name)
+    : container_(container),
+      item_(std::move(item)),
+      header_row_controls_view_(header_row_controls_view),
+      default_app_name_(default_app_name) {
+  DCHECK(container_);
+
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::kVertical, gfx::Insets(), 0));
 
-  SetNativeTheme(ui::NativeThemeDarkAura::instance());
-  // |controls_button_view_| has the common notification control buttons.
-  control_buttons_view_ =
-      std::make_unique<message_center::NotificationControlButtonsView>(this);
-  control_buttons_view_->set_owned_by_client();
-
-  // |header_row_| contains app_icon, app_name, control buttons, etc.
   auto header_row =
       std::make_unique<message_center::NotificationHeaderView>(this);
-  header_row->AddChildView(control_buttons_view_.get());
-  header_row->SetAppName(
-      message_center::MessageCenter::Get()->GetSystemNotificationAppName());
+
+  if (header_row_controls_view_)
+    header_row->AddChildView(header_row_controls_view_);
+
+  header_row->SetAppName(default_app_name_);
   header_row->ClearAppIcon();
   header_row->SetProperty(views::kMarginsKey,
                           new gfx::Insets(kMediaNotificationHeaderTopInset,
@@ -205,7 +204,6 @@
       message_center::kNotificationCornerRadius, kMediaImageMaxWidthPct));
 
   UpdateForegroundColor();
-  UpdateControlButtonsVisibilityWithNotification(notification);
   UpdateCornerRadius(message_center::kNotificationCornerRadius,
                      message_center::kNotificationCornerRadius);
   UpdateViewForExpandedState();
@@ -219,22 +217,6 @@
     item_->SetView(nullptr);
 }
 
-void MediaNotificationView::UpdateWithNotification(
-    const message_center::Notification& notification) {
-  MessageView::UpdateWithNotification(notification);
-
-  UpdateControlButtonsVisibilityWithNotification(notification);
-
-  PreferredSizeChanged();
-  Layout();
-  SchedulePaint();
-}
-
-message_center::NotificationControlButtonsView*
-MediaNotificationView::GetControlButtonsView() const {
-  return control_buttons_view_.get();
-}
-
 void MediaNotificationView::SetExpanded(bool expanded) {
   if (expanded_ == expanded)
     return;
@@ -264,19 +246,6 @@
     node_data->SetName(accessible_name_);
 }
 
-void MediaNotificationView::OnMouseEvent(ui::MouseEvent* event) {
-  switch (event->type()) {
-    case ui::ET_MOUSE_ENTERED:
-    case ui::ET_MOUSE_EXITED:
-      UpdateControlButtonsVisibility();
-      break;
-    default:
-      break;
-  }
-
-  View::OnMouseEvent(event);
-}
-
 void MediaNotificationView::ButtonPressed(views::Button* sender,
                                           const ui::Event& event) {
   if (sender == header_row_) {
@@ -315,11 +284,9 @@
 
 void MediaNotificationView::UpdateWithMediaMetadata(
     const media_session::MediaMetadata& metadata) {
-  header_row_->SetAppName(
-      metadata.source_title.empty()
-          ? message_center::MessageCenter::Get()->GetSystemNotificationAppName()
-          : metadata.source_title);
-
+  header_row_->SetAppName(metadata.source_title.empty()
+                              ? default_app_name_
+                              : metadata.source_title);
   title_label_->SetText(metadata.title);
   artist_label_->SetText(metadata.artist);
   header_row_->SetSummaryText(metadata.album);
@@ -386,16 +353,6 @@
   }
 }
 
-void MediaNotificationView::UpdateControlButtonsVisibilityWithNotification(
-    const message_center::Notification& notification) {
-  // Media notifications do not use the settings and snooze buttons.
-  DCHECK(!notification.should_show_settings_button());
-  DCHECK(!notification.should_show_snooze_button());
-
-  control_buttons_view_->ShowCloseButton(!notification.pinned());
-  UpdateControlButtonsVisibility();
-}
-
 void MediaNotificationView::UpdateActionButtonsVisibility() {
   std::set<MediaSessionAction> visible_actions =
       CalculateVisibleActions(IsActuallyExpanded());
@@ -447,6 +404,8 @@
   header_row_->SetExpanded(expanded);
 
   UpdateActionButtonsVisibility();
+
+  container_->OnExpanded(expanded);
 }
 
 void MediaNotificationView::CreateMediaButton(
diff --git a/ash/media/media_notification_view.h b/ash/media/media_notification_view.h
index b793fca..86b3381 100644
--- a/ash/media/media_notification_view.h
+++ b/ash/media/media_notification_view.h
@@ -8,7 +8,6 @@
 #include "ash/ash_export.h"
 #include "base/memory/weak_ptr.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
-#include "ui/message_center/views/message_view.h"
 #include "ui/views/controls/button/button.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/label.h"
@@ -35,13 +34,12 @@
 namespace ash {
 
 class MediaNotificationBackground;
+class MediaNotificationContainer;
 class MediaNotificationItem;
 
-// MediaNotificationView will show up as a custom notification. It will show the
-// currently playing media and provide playback controls. There will also be
-// control buttons (e.g. close) in the top right corner that will hide and show
-// if the notification is hovered.
-class ASH_EXPORT MediaNotificationView : public message_center::MessageView,
+// MediaNotificationView will show up as a custom view. It will show the
+// currently playing media and provide playback controls.
+class ASH_EXPORT MediaNotificationView : public views::View,
                                          public views::ButtonListener {
  public:
   // The name of the histogram used when recorded whether the artwork was
@@ -62,21 +60,17 @@
     kMaxValue = kCount,
   };
 
-  MediaNotificationView(const message_center::Notification& notification,
-                        base::WeakPtr<MediaNotificationItem> item);
+  MediaNotificationView(MediaNotificationContainer* container,
+                        base::WeakPtr<MediaNotificationItem> item,
+                        views::View* header_row_controls_view,
+                        const base::string16& default_app_name);
   ~MediaNotificationView() override;
 
-  // message_center::MessageView:
-  void UpdateWithNotification(
-      const message_center::Notification& notification) override;
-  message_center::NotificationControlButtonsView* GetControlButtonsView()
-      const override;
-  void SetExpanded(bool expanded) override;
-  void UpdateCornerRadius(int top_radius, int bottom_radius) override;
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+  void SetExpanded(bool expanded);
+  void UpdateCornerRadius(int top_radius, int bottom_radius);
 
   // views::View:
-  void OnMouseEvent(ui::MouseEvent* event) override;
+  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
 
   // views::ButtonListener:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
@@ -92,9 +86,6 @@
  private:
   friend class MediaNotificationViewTest;
 
-  void UpdateControlButtonsVisibilityWithNotification(
-      const message_center::Notification& notification);
-
   // Creates an image button with an icon that matches |action| and adds it
   // to |button_row_|. When clicked it will trigger |action| on the session.
   // |accessible_name| is the text used for screen readers.
@@ -114,11 +105,19 @@
 
   void UpdateForegroundColor();
 
+  // Container that receives OnExpanded events.
+  MediaNotificationContainer* const container_;
+
+  // Keeps track of media metadata and controls the session when buttons are
+  // clicked.
   base::WeakPtr<MediaNotificationItem> item_;
 
-  // View containing close and settings buttons.
-  std::unique_ptr<message_center::NotificationControlButtonsView>
-      control_buttons_view_;
+  // Optional View that is put into the header row. E.g. in Ash we show
+  // notification control buttons.
+  views::View* header_row_controls_view_;
+
+  // String to set as the app name of the header when there is no source title.
+  base::string16 default_app_name_;
 
   bool has_artwork_ = false;
 
diff --git a/ash/media/media_notification_view_unittest.cc b/ash/media/media_notification_view_unittest.cc
index f41b17b..2e68628 100644
--- a/ash/media/media_notification_view_unittest.cc
+++ b/ash/media/media_notification_view_unittest.cc
@@ -6,27 +6,20 @@
 
 #include <memory>
 
-#include "ash/focus_cycler.h"
 #include "ash/media/media_notification_background.h"
 #include "ash/media/media_notification_constants.h"
-#include "ash/media/media_notification_controller_impl.h"
+#include "ash/media/media_notification_container.h"
+#include "ash/media/media_notification_controller.h"
 #include "ash/media/media_notification_item.h"
-#include "ash/public/cpp/ash_features.h"
-#include "ash/public/cpp/shell_window_ids.h"
-#include "ash/shell.h"
-#include "ash/system/status_area_widget.h"
-#include "ash/system/status_area_widget_test_helper.h"
-#include "ash/system/unified/unified_system_tray.h"
-#include "ash/test/ash_test_base.h"
 #include "base/bind.h"
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/unguessable_token.h"
 #include "services/media_session/public/cpp/test/test_media_controller.h"
 #include "services/media_session/public/mojom/audio_focus.mojom.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/events/test/event_generator.h"
@@ -36,11 +29,15 @@
 #include "ui/message_center/views/notification_control_buttons_view.h"
 #include "ui/message_center/views/notification_header_view.h"
 #include "ui/views/controls/image_view.h"
+#include "ui/views/test/views_test_base.h"
 
 namespace ash {
 
 using media_session::mojom::MediaSessionAction;
 using media_session::test::TestMediaController;
+using testing::_;
+using testing::Expectation;
+using testing::Invoke;
 
 namespace {
 
@@ -51,101 +48,108 @@
 // The title artist row should always have the same height.
 const int kMediaTitleArtistRowExpectedHeight = 48;
 
+const char kTestDefaultAppName[] = "default app name";
 const char kTestAppName[] = "app name";
 
+const gfx::Size kWidgetSize(500, 500);
+const gfx::Size kViewSize(400, 400);
+
 // Checks if the view class name is used by a media button.
 bool IsMediaButtonType(const char* class_name) {
   return class_name == views::ImageButton::kViewClassName ||
          class_name == views::ToggleImageButton::kViewClassName;
 }
 
+class MockMediaNotificationController : public MediaNotificationController {
+ public:
+  MockMediaNotificationController() = default;
+  ~MockMediaNotificationController() = default;
+
+  // MediaNotificationController implementation.
+  MOCK_METHOD1(ShowNotification, void(const std::string& id));
+  MOCK_METHOD1(HideNotification, void(const std::string& id));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockMediaNotificationController);
+};
+
+class MockMediaNotificationContainer : public MediaNotificationContainer {
+ public:
+  MockMediaNotificationContainer() = default;
+  ~MockMediaNotificationContainer() = default;
+
+  // MediaNotificationContainer implementation.
+  MOCK_METHOD1(OnExpanded, void(bool expanded));
+
+  MediaNotificationView* view() const { return view_.get(); }
+  void SetView(std::unique_ptr<MediaNotificationView> view) {
+    view_ = std::move(view);
+  }
+
+ private:
+  std::unique_ptr<MediaNotificationView> view_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockMediaNotificationContainer);
+};
+
 }  // namespace
 
-class MediaNotificationViewTest : public AshTestBase {
+class MediaNotificationViewTest : public views::ViewsTestBase {
  public:
   MediaNotificationViewTest() = default;
   ~MediaNotificationViewTest() override = default;
 
-  // AshTestBase
   void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(
-        features::kMediaSessionNotification);
-
-    AshTestBase::SetUp();
+    views::ViewsTestBase::SetUp();
 
     request_id_ = base::UnguessableToken::Create();
 
-    ShowNotificationAndCaptureView(
+    // Create a new MediaNotificationView whenever the MediaNotificationItem
+    // says to show the notification.
+    EXPECT_CALL(controller_, ShowNotification(request_id_.ToString()))
+        .WillRepeatedly(
+            InvokeWithoutArgs(this, &MediaNotificationViewTest::CreateView));
+
+    // Create a widget to show on the screen for testing screen coordinates and
+    // focus.
+    widget_ = std::make_unique<views::Widget>();
+    views::Widget::InitParams params =
+        CreateParams(views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+    params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+    params.bounds = gfx::Rect(kWidgetSize);
+    widget_->Init(params);
+    widget_->Show();
+
+    CreateViewFromMediaSessionInfo(
         media_session::mojom::MediaSessionInfo::New());
   }
 
-  void ShowNotificationAndCaptureView(
+  void CreateViewFromMediaSessionInfo(
       media_session::mojom::MediaSessionInfoPtr session_info) {
-    view_ = nullptr;
-
-    // Set a custom view factory to create and capture the notification view.
-    message_center::MessageViewFactory::
-        ClearCustomNotificationViewFactoryForTest(
-            kMediaSessionNotificationCustomViewType);
-    message_center::MessageViewFactory::SetCustomNotificationViewFactory(
-        kMediaSessionNotificationCustomViewType,
-        base::BindRepeating(
-            &MediaNotificationViewTest::CreateAndCaptureCustomView,
-            base::Unretained(this)));
-
-    // Show the notification.
-    media_session::mojom::AudioFocusRequestStatePtr session(
-        media_session::mojom::AudioFocusRequestState::New());
-    session->session_info = std::move(session_info);
-    session->session_info->is_controllable = true;
-    session->request_id = request_id_;
-    Shell::Get()->media_notification_controller()->OnFocusGained(
-        std::move(session));
+    session_info->is_controllable = true;
+    media_session::mojom::MediaControllerPtr controller;
+    item_ = std::make_unique<MediaNotificationItem>(
+        &controller_, request_id_.ToString(), std::string(),
+        std::move(controller), std::move(session_info));
 
     // Update the metadata.
     media_session::MediaMetadata metadata;
     metadata.title = base::ASCIIToUTF16("title");
     metadata.artist = base::ASCIIToUTF16("artist");
-    GetItem()->MediaSessionMetadataChanged(metadata);
-
-    message_center::Notification* notification =
-        message_center::MessageCenter::Get()->FindVisibleNotificationById(
-            request_id_.ToString());
-    ASSERT_TRUE(notification);
-
-    // Open the system tray. This will trigger the view to be created.
-    auto* unified_system_tray =
-        StatusAreaWidgetTestHelper::GetStatusAreaWidget()
-            ->unified_system_tray();
-    unified_system_tray->SetTrayEnabled(true);
-    unified_system_tray->ShowBubble(false /* show_by_click */);
-    unified_system_tray->ActivateBubble();
-
-    // Check that the view was captured.
-    ASSERT_TRUE(view_);
-    view_->set_notify_enter_exit_on_child(true);
+    item_->MediaSessionMetadataChanged(metadata);
 
     // Inject the test media controller into the item.
-    ASSERT_TRUE(GetItem());
     media_controller_ = std::make_unique<TestMediaController>();
-    GetItem()->SetMediaControllerForTesting(
+    item_->SetMediaControllerForTesting(
         media_controller_->CreateMediaControllerPtr());
   }
 
   void TearDown() override {
-    view_ = nullptr;
+    widget_.reset();
 
     actions_.clear();
 
-    message_center::MessageViewFactory::
-        ClearCustomNotificationViewFactoryForTest(
-            kMediaSessionNotificationCustomViewType);
-
-    AshTestBase::TearDown();
-  }
-
-  bool IsControlButtonsViewVisible() const {
-    return view()->GetControlButtonsView()->layer()->opacity() > 0;
+    views::ViewsTestBase::TearDown();
   }
 
   void EnableAllActions() {
@@ -170,27 +174,29 @@
     NotifyUpdatedActions();
   }
 
-  MediaNotificationView* view() const { return view_; }
+  MockMediaNotificationContainer& container() { return container_; }
+
+  MediaNotificationView* view() const { return container_.view(); }
 
   TestMediaController* media_controller() const {
     return media_controller_.get();
   }
 
   message_center::NotificationHeaderView* header_row() const {
-    return view_->header_row_;
+    return view()->header_row_;
   }
 
   const base::string16& accessible_name() const {
-    return view_->accessible_name_;
+    return view()->accessible_name_;
   }
 
-  views::View* button_row() const { return view_->button_row_; }
+  views::View* button_row() const { return view()->button_row_; }
 
-  views::View* title_artist_row() const { return view_->title_artist_row_; }
+  views::View* title_artist_row() const { return view()->title_artist_row_; }
 
-  views::Label* title_label() const { return view_->title_label_; }
+  views::Label* title_label() const { return view()->title_label_; }
 
-  views::Label* artist_label() const { return view_->artist_label_; }
+  views::Label* artist_label() const { return view()->artist_label_; }
 
   views::Button* GetButtonForAction(MediaSessionAction action) const {
     const auto& children = button_row()->children();
@@ -205,39 +211,41 @@
     return GetButtonForAction(action)->GetVisible();
   }
 
-  MediaNotificationItem* GetItem() const {
-    return Shell::Get()->media_notification_controller()->GetItem(
-        request_id_.ToString());
-  }
-
-  const base::UnguessableToken& request_id() const { return request_id_; }
+  MediaNotificationItem* GetItem() const { return item_.get(); }
 
   const gfx::ImageSkia& GetArtworkImage() const {
-    return view_->GetMediaNotificationBackground()->artwork_;
+    return view()->GetMediaNotificationBackground()->artwork_;
   }
 
   const gfx::ImageSkia& GetAppIcon() const {
-    return view_->header_row_->app_icon_for_testing();
+    return header_row()->app_icon_for_testing();
   }
 
   bool expand_button_enabled() const {
     return header_row()->expand_button()->GetVisible();
   }
 
-  bool IsActuallyExpanded() const { return view_->IsActuallyExpanded(); }
+  bool IsActuallyExpanded() const { return view()->IsActuallyExpanded(); }
 
   void SimulateButtonClick(MediaSessionAction action) {
     views::Button* button = GetButtonForAction(action);
     EXPECT_TRUE(button->GetVisible());
 
-    view_->ButtonPressed(
+    view()->ButtonPressed(
         button, ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
                                ui::EventTimeForNow(), 0, 0));
   }
 
+  void SimulateHeaderClick() {
+    view()->ButtonPressed(
+        header_row(),
+        ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
+                       ui::EventTimeForNow(), 0, 0));
+  }
+
   void SimulateTab() {
-    ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
-    generator.PressKey(ui::KeyboardCode::VKEY_TAB, ui::EventFlags::EF_NONE);
+    ui::KeyEvent pressed_tab(ui::ET_KEY_PRESSED, ui::VKEY_TAB, ui::EF_NONE);
+    view()->GetFocusManager()->OnKeyEvent(pressed_tab);
   }
 
   void ExpectHistogramActionRecorded(MediaSessionAction action) {
@@ -260,57 +268,45 @@
   }
 
  private:
-  std::unique_ptr<message_center::MessageView> CreateAndCaptureCustomView(
-      const message_center::Notification& notification) {
-    auto view = std::make_unique<MediaNotificationView>(
-        notification, GetItem()->GetWeakPtr());
-    view_ = view.get();
-    return view;
-  }
-
   void NotifyUpdatedActions() {
-    GetItem()->MediaSessionActionsChanged(
+    item_->MediaSessionActionsChanged(
         std::vector<MediaSessionAction>(actions_.begin(), actions_.end()));
   }
 
-  base::UnguessableToken request_id_;
+  void CreateView() {
+    // Create a MediaNotificationView.
+    auto view = std::make_unique<MediaNotificationView>(
+        &container_, item_->GetWeakPtr(),
+        nullptr /* header_row_controls_view */,
+        base::ASCIIToUTF16(kTestDefaultAppName));
+    view->SetSize(kViewSize);
+    view->set_owned_by_client();
 
-  base::test::ScopedFeatureList scoped_feature_list_;
+    // Display it in |widget_|.
+    widget_->SetContentsView(view.get());
+
+    // Associate it with |container_|.
+    container_.SetView(std::move(view));
+  }
+
+  base::UnguessableToken request_id_;
 
   base::HistogramTester histogram_tester_;
 
   std::set<MediaSessionAction> actions_;
 
   std::unique_ptr<TestMediaController> media_controller_;
+  MockMediaNotificationContainer container_;
+  MockMediaNotificationController controller_;
+  std::unique_ptr<MediaNotificationItem> item_;
   std::unique_ptr<views::Widget> widget_;
-  MediaNotificationView* view_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(MediaNotificationViewTest);
 };
 
-TEST_F(MediaNotificationViewTest, ShowControlsOnHover) {
-  EXPECT_FALSE(IsControlButtonsViewVisible());
-
-  {
-    gfx::Point cursor_location(1, 1);
-    views::View::ConvertPointToScreen(view(), &cursor_location);
-    GetEventGenerator()->MoveMouseTo(cursor_location);
-    GetEventGenerator()->SendMouseEnter();
-  }
-
-  EXPECT_TRUE(IsControlButtonsViewVisible());
-
-  {
-    gfx::Point cursor_location(-1, -1);
-    views::View::ConvertPointToScreen(view(), &cursor_location);
-    GetEventGenerator()->MoveMouseTo(cursor_location);
-    GetEventGenerator()->SendMouseExit();
-  }
-
-  EXPECT_FALSE(IsControlButtonsViewVisible());
-}
-
 TEST_F(MediaNotificationViewTest, ButtonsSanityCheck) {
+  view()->SetExpanded(true);
+
   EnableAllActions();
 
   EXPECT_TRUE(button_row()->GetVisible());
@@ -339,9 +335,10 @@
 }
 
 TEST_F(MediaNotificationViewTest, ButtonsFocusCheck) {
+  // Expand and enable all actions to show all buttons.
+  view()->SetExpanded(true);
   EnableAllActions();
 
-  Shell::Get()->focus_cycler()->FocusWidget(view()->GetWidget());
   views::FocusManager* focus_manager = view()->GetFocusManager();
 
   {
@@ -467,54 +464,6 @@
   ExpectHistogramActionRecorded(MediaSessionAction::kSeekForward);
 }
 
-TEST_F(MediaNotificationViewTest, ClickNotification) {
-  EXPECT_EQ(0, media_controller()->toggle_suspend_resume_count());
-
-  gfx::Point cursor_location(1, 1);
-  views::View::ConvertPointToScreen(view(), &cursor_location);
-  GetEventGenerator()->MoveMouseTo(cursor_location);
-  GetEventGenerator()->ClickLeftButton();
-  GetItem()->FlushForTesting();
-
-  EXPECT_EQ(0, media_controller()->toggle_suspend_resume_count());
-}
-
-TEST_F(MediaNotificationViewTest, PlayToggle_FromActiveSessionChanged) {
-  {
-    views::ToggleImageButton* button = static_cast<views::ToggleImageButton*>(
-        GetButtonForAction(MediaSessionAction::kPlay));
-    ASSERT_EQ(views::ToggleImageButton::kViewClassName, button->GetClassName());
-    EXPECT_FALSE(button->toggled_for_testing());
-  }
-
-  media_session::mojom::AudioFocusRequestStatePtr session(
-      media_session::mojom::AudioFocusRequestState::New());
-  session->request_id = request_id();
-  Shell::Get()->media_notification_controller()->OnFocusLost(
-      std::move(session));
-
-  // Disable the tray and run the loop to make sure that the existing view is
-  // destroyed.
-  StatusAreaWidgetTestHelper::GetStatusAreaWidget()
-      ->unified_system_tray()
-      ->SetTrayEnabled(false);
-  base::RunLoop().RunUntilIdle();
-
-  media_session::mojom::MediaSessionInfoPtr session_info(
-      media_session::mojom::MediaSessionInfo::New());
-  session_info->playback_state =
-      media_session::mojom::MediaPlaybackState::kPlaying;
-
-  ShowNotificationAndCaptureView(std::move(session_info));
-
-  {
-    views::ToggleImageButton* button = static_cast<views::ToggleImageButton*>(
-        GetButtonForAction(MediaSessionAction::kPause));
-    ASSERT_EQ(views::ToggleImageButton::kViewClassName, button->GetClassName());
-    EXPECT_TRUE(button->toggled_for_testing());
-  }
-}
-
 TEST_F(MediaNotificationViewTest, PlayToggle_FromObserver_Empty) {
   EnableAction(MediaSessionAction::kPlay);
 
@@ -574,6 +523,8 @@
 }
 
 TEST_F(MediaNotificationViewTest, MetadataIsDisplayed) {
+  view()->SetExpanded(true);
+
   EnableAllActions();
 
   EXPECT_TRUE(title_artist_row()->GetVisible());
@@ -602,6 +553,7 @@
   metadata.album = base::ASCIIToUTF16("album");
 
   GetItem()->MediaSessionMetadataChanged(metadata);
+  view()->SetExpanded(true);
 
   EXPECT_TRUE(title_artist_row()->GetVisible());
   EXPECT_TRUE(title_label()->GetVisible());
@@ -623,9 +575,8 @@
 }
 
 TEST_F(MediaNotificationViewTest, UpdateMetadata_AppName) {
-  EXPECT_EQ(
-      message_center::MessageCenter::Get()->GetSystemNotificationAppName(),
-      header_row()->app_name_for_testing());
+  EXPECT_EQ(base::ASCIIToUTF16(kTestDefaultAppName),
+            header_row()->app_name_for_testing());
 
   {
     media_session::MediaMetadata metadata;
@@ -645,9 +596,8 @@
     GetItem()->MediaSessionMetadataChanged(metadata);
   }
 
-  EXPECT_EQ(
-      message_center::MessageCenter::Get()->GetSystemNotificationAppName(),
-      header_row()->app_name_for_testing());
+  EXPECT_EQ(base::ASCIIToUTF16(kTestDefaultAppName),
+            header_row()->app_name_for_testing());
 }
 
 TEST_F(MediaNotificationViewTest, Buttons_WhenCollapsed) {
@@ -691,25 +641,16 @@
 }
 
 TEST_F(MediaNotificationViewTest, ClickHeader_ToggleExpand) {
+  view()->SetExpanded(true);
   EnableAllActions();
 
   EXPECT_TRUE(IsActuallyExpanded());
 
-  {
-    gfx::Point cursor_location(1, 1);
-    views::View::ConvertPointToScreen(header_row(), &cursor_location);
-    GetEventGenerator()->MoveMouseTo(cursor_location.x(), cursor_location.y());
-    GetEventGenerator()->ClickLeftButton();
-  }
+  SimulateHeaderClick();
 
   EXPECT_FALSE(IsActuallyExpanded());
 
-  {
-    gfx::Point cursor_location(1, 1);
-    views::View::ConvertPointToScreen(header_row(), &cursor_location);
-    GetEventGenerator()->MoveMouseTo(cursor_location.x(), cursor_location.y());
-    GetEventGenerator()->ClickLeftButton();
-  }
+  SimulateHeaderClick();
 
   EXPECT_TRUE(IsActuallyExpanded());
 }
@@ -722,7 +663,7 @@
   EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kSeekBackward));
 }
 
-TEST_F(MediaNotificationViewTest, ActionButtonsToggleVisbility) {
+TEST_F(MediaNotificationViewTest, ActionButtonsToggleVisibility) {
   EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kNextTrack));
 
   EnableAction(MediaSessionAction::kNextTrack);
@@ -807,6 +748,8 @@
 }
 
 TEST_F(MediaNotificationViewTest, ExpandablePlayPauseActionCountsOnce) {
+  view()->SetExpanded(true);
+
   EXPECT_FALSE(IsActuallyExpanded());
   EXPECT_FALSE(expand_button_enabled());
 
@@ -834,6 +777,8 @@
 }
 
 TEST_F(MediaNotificationViewTest, BecomeExpandableAndWasNotExpandable) {
+  view()->SetExpanded(true);
+
   EXPECT_FALSE(IsActuallyExpanded());
   EXPECT_FALSE(expand_button_enabled());
 
@@ -844,6 +789,8 @@
 }
 
 TEST_F(MediaNotificationViewTest, BecomeExpandableButWasAlreadyExpandable) {
+  view()->SetExpanded(true);
+
   EXPECT_FALSE(IsActuallyExpanded());
   EXPECT_FALSE(expand_button_enabled());
 
@@ -859,6 +806,8 @@
 }
 
 TEST_F(MediaNotificationViewTest, BecomeNotExpandableAndWasExpandable) {
+  view()->SetExpanded(true);
+
   EXPECT_FALSE(IsActuallyExpanded());
   EXPECT_FALSE(expand_button_enabled());
 
@@ -878,6 +827,8 @@
 
 TEST_F(MediaNotificationViewTest,
        BecomeNotExpandableButWasAlreadyNotExpandable) {
+  view()->SetExpanded(true);
+
   EXPECT_FALSE(IsActuallyExpanded());
   EXPECT_FALSE(expand_button_enabled());
 
@@ -907,6 +858,33 @@
   EXPECT_GT(button_x, button->GetBoundsInScreen().x());
 }
 
+TEST_F(MediaNotificationViewTest, NotifysContainerOfExpandedState) {
+  // Track the expanded state given to |container_|.
+  bool expanded = false;
+  EXPECT_CALL(container(), OnExpanded(_))
+      .WillRepeatedly(Invoke([&expanded](bool exp) { expanded = exp; }));
+
+  // Expand the view implicitly via |EnableAllActions()|.
+  view()->SetExpanded(true);
+  EnableAllActions();
+  EXPECT_TRUE(expanded);
+
+  // Explicitly contract the view.
+  view()->SetExpanded(false);
+  EXPECT_FALSE(expanded);
+
+  // Explicitly expand the view.
+  view()->SetExpanded(true);
+  EXPECT_TRUE(expanded);
+
+  // Implicitly contract the view by removing available actions.
+  DisableAction(MediaSessionAction::kPreviousTrack);
+  DisableAction(MediaSessionAction::kNextTrack);
+  DisableAction(MediaSessionAction::kSeekBackward);
+  DisableAction(MediaSessionAction::kSeekForward);
+  EXPECT_FALSE(expanded);
+}
+
 TEST_F(MediaNotificationViewTest, AccessibleNodeData) {
   ui::AXNodeData data;
   view()->GetAccessibleNodeData(&data);
diff --git a/ash/metrics/demo_session_metrics_recorder.cc b/ash/metrics/demo_session_metrics_recorder.cc
index 098e3af..25450e76 100644
--- a/ash/metrics/demo_session_metrics_recorder.cc
+++ b/ash/metrics/demo_session_metrics_recorder.cc
@@ -45,14 +45,16 @@
   // Each version of the Highlights app is bucketed into the same value.
   if (app_id == extension_misc::kHighlightsAppId ||
       app_id == extension_misc::kHighlightsEveAppId ||
-      app_id == extension_misc::kHighlightsNocturneAppId) {
+      app_id == extension_misc::kHighlightsNocturneAppId ||
+      app_id == extension_misc::kHighlightsAltAppId) {
     return DemoModeApp::kHighlights;
   }
 
   // Each version of the Screensaver app is bucketed into the same value.
   if (app_id == extension_misc::kScreensaverAppId ||
       app_id == extension_misc::kScreensaverEveAppId ||
-      app_id == extension_misc::kScreensaverNocturneAppId) {
+      app_id == extension_misc::kScreensaverNocturneAppId ||
+      app_id == extension_misc::kScreensaverAltAppId) {
     return DemoModeApp::kScreensaver;
   }
 
diff --git a/ash/mojo_interface_factory.cc b/ash/mojo_interface_factory.cc
index 11059f5..e0e97b7 100644
--- a/ash/mojo_interface_factory.cc
+++ b/ash/mojo_interface_factory.cc
@@ -19,7 +19,6 @@
 #include "ash/events/event_rewriter_controller.h"
 #include "ash/highlighter/highlighter_controller.h"
 #include "ash/ime/ime_controller.h"
-#include "ash/ime/ime_engine_factory_registry.h"
 #include "ash/keyboard/ash_keyboard_controller.h"
 #include "ash/keyboard/ui/keyboard_controller.h"
 #include "ash/kiosk_next/kiosk_next_shell_controller.h"
@@ -124,11 +123,6 @@
   Shell::Get()->ime_controller()->BindRequest(std::move(request));
 }
 
-void BindImeEngineFactoryRegistryRequestOnMainThread(
-    ime::mojom::ImeEngineFactoryRegistryRequest request) {
-  Shell::Get()->ime_engine_factory_registry()->BindRequest(std::move(request));
-}
-
 void BindKeyboardControllerRequestOnMainThread(
     mojom::KeyboardControllerRequest request) {
   Shell::Get()->ash_keyboard_controller()->BindRequest(std::move(request));
@@ -256,9 +250,6 @@
       base::BindRepeating(&BindImeControllerRequestOnMainThread),
       main_thread_task_runner);
   registry->AddInterface(
-      base::BindRepeating(&BindImeEngineFactoryRegistryRequestOnMainThread),
-      main_thread_task_runner);
-  registry->AddInterface(
       base::BindRepeating(&BindKeyboardControllerRequestOnMainThread),
       main_thread_task_runner);
   registry->AddInterface(
diff --git a/ash/multi_device_setup/multi_device_notification_presenter.cc b/ash/multi_device_setup/multi_device_notification_presenter.cc
index f2ebbf04..4e7f2dd1 100644
--- a/ash/multi_device_setup/multi_device_notification_presenter.cc
+++ b/ash/multi_device_setup/multi_device_notification_presenter.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "ash/public/cpp/notification_utils.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/public/cpp/vector_icons/vector_icons.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
@@ -59,15 +60,12 @@
 
 void MultiDeviceNotificationPresenter::OpenUiDelegate::
     OpenMultiDeviceSetupUi() {
-  Shell::Get()->system_tray_model()->client_ptr()->ShowMultiDeviceSetup();
+  Shell::Get()->system_tray_model()->client()->ShowMultiDeviceSetup();
 }
 
 void MultiDeviceNotificationPresenter::OpenUiDelegate::
     OpenConnectedDevicesSettings() {
-  Shell::Get()
-      ->system_tray_model()
-      ->client_ptr()
-      ->ShowConnectedDevicesSettings();
+  Shell::Get()->system_tray_model()->client()->ShowConnectedDevicesSettings();
 }
 
 // static
diff --git a/ash/network_connect_delegate_mus.cc b/ash/network_connect_delegate_mus.cc
index a0b8e5b..b1b9d669 100644
--- a/ash/network_connect_delegate_mus.cc
+++ b/ash/network_connect_delegate_mus.cc
@@ -4,6 +4,7 @@
 
 #include "ash/network_connect_delegate_mus.h"
 
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/shell.h"
 #include "ash/system/model/system_tray_model.h"
 #include "base/logging.h"
@@ -16,14 +17,12 @@
 
 void NetworkConnectDelegateMus::ShowNetworkConfigure(
     const std::string& network_id) {
-  Shell::Get()->system_tray_model()->client_ptr()->ShowNetworkConfigure(
-      network_id);
+  Shell::Get()->system_tray_model()->client()->ShowNetworkConfigure(network_id);
 }
 
 void NetworkConnectDelegateMus::ShowNetworkSettings(
     const std::string& network_id) {
-  Shell::Get()->system_tray_model()->client_ptr()->ShowNetworkSettings(
-      network_id);
+  Shell::Get()->system_tray_model()->client()->ShowNetworkSettings(network_id);
 }
 
 bool NetworkConnectDelegateMus::ShowEnrollNetwork(
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index e0a791b..00840b9 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -133,6 +133,9 @@
     "split_view.h",
     "stylus_utils.cc",
     "stylus_utils.h",
+    "system_tray.cc",
+    "system_tray.h",
+    "system_tray_client.h",
     "system_tray_focus_observer.h",
     "tablet_mode.cc",
     "tablet_mode.h",
diff --git a/ash/public/cpp/ash_pref_names.cc b/ash/public/cpp/ash_pref_names.cc
index d3d901c..e72d8f8 100644
--- a/ash/public/cpp/ash_pref_names.cc
+++ b/ash/public/cpp/ash_pref_names.cc
@@ -88,6 +88,10 @@
 // regardless of the state of a11y features.
 const char kShouldAlwaysShowAccessibilityMenu[] = "settings.a11y.enable_menu";
 
+// A boolean pref that stores whether the user is eligible to start the Kiosk
+// Next shell.
+const char kKioskNextShellEligible[] = "ash.kiosk_next_shell.eligible";
+
 // A boolean pref that stores whether the Kiosk Next Shell is enabled. When it
 // is, we start it after sign in.
 const char kKioskNextShellEnabled[] = "ash.kiosk_next_shell.enabled";
diff --git a/ash/public/cpp/ash_pref_names.h b/ash/public/cpp/ash_pref_names.h
index e5bddef..b016bac 100644
--- a/ash/public/cpp/ash_pref_names.h
+++ b/ash/public/cpp/ash_pref_names.h
@@ -36,6 +36,7 @@
 ASH_PUBLIC_EXPORT extern const char kAccessibilityDictationEnabled[];
 ASH_PUBLIC_EXPORT extern const char kShouldAlwaysShowAccessibilityMenu[];
 
+ASH_PUBLIC_EXPORT extern const char kKioskNextShellEligible[];
 ASH_PUBLIC_EXPORT extern const char kKioskNextShellEnabled[];
 
 ASH_PUBLIC_EXPORT extern const char kDockedMagnifierEnabled[];
diff --git a/ash/public/cpp/ash_view_ids.h b/ash/public/cpp/ash_view_ids.h
index f1e83c0..8a030cc 100644
--- a/ash/public/cpp/ash_view_ids.h
+++ b/ash/public/cpp/ash_view_ids.h
@@ -19,6 +19,8 @@
   VIEW_ID_ACCESSIBILITY_VIRTUAL_KEYBOARD_ENABLED,
   // Accessibility feature pod button in main view.
   VIEW_ID_ACCESSIBILITY_TRAY_ITEM,
+  // System tray AddUserButton in UserChooserView.
+  VIEW_ID_ADD_USER_BUTTON,
   VIEW_ID_BLUETOOTH_DEFAULT_VIEW,
   // System tray casting row elements.
   VIEW_ID_CAST_CAST_VIEW,
@@ -38,6 +40,15 @@
   VIEW_ID_TRAY_UPDATE_ICON,
   // System tray menu item label for updates (e.g. "Restart to update").
   VIEW_ID_TRAY_UPDATE_MENU_LABEL,
+  // System tray UserAvatarButton in TopShortcutsView.
+  VIEW_ID_USER_AVATAR_BUTTON,
+
+  // Start and end of system tray UserItemButton in UserChooserView. First
+  // user gets VIEW_ID_USER_ITEM_BUTTON_START. DCHECKs if the number of user
+  // is more than 10.
+  VIEW_ID_USER_ITEM_BUTTON_START,
+  VIEW_ID_USER_ITEM_BUTTON_END = VIEW_ID_USER_ITEM_BUTTON_START + 10,
+
   VIEW_ID_USER_VIEW_MEDIA_INDICATOR,
   // Keep alphabetized.
 };
diff --git a/ash/public/cpp/manifest.cc b/ash/public/cpp/manifest.cc
index 2484c71..16dd15c 100644
--- a/ash/public/cpp/manifest.cc
+++ b/ash/public/cpp/manifest.cc
@@ -6,7 +6,6 @@
 
 #include "ash/public/interfaces/accessibility_controller.mojom.h"
 #include "ash/public/interfaces/accessibility_focus_ring_controller.mojom.h"
-#include "ash/public/interfaces/app_list.mojom.h"
 #include "ash/public/interfaces/ash_message_center_controller.mojom.h"
 #include "ash/public/interfaces/assistant_controller.mojom.h"
 #include "ash/public/interfaces/assistant_volume_control.mojom.h"
@@ -39,7 +38,6 @@
 #include "services/preferences/public/mojom/preferences.mojom.h"
 #include "services/service_manager/public/cpp/manifest_builder.h"
 #include "services/viz/public/interfaces/constants.mojom.h"
-#include "ui/base/ime/mojo/ime_engine_factory_registry.mojom.h"
 
 namespace ash {
 
@@ -74,14 +72,13 @@
                   mojom::KioskNextShellController,
                   mojom::CrosDisplayConfigController,
                   mojom::EventRewriterController, mojom::HighlighterController,
-                  mojom::ImeController, ime::mojom::ImeEngineFactoryRegistry,
-                  mojom::KeyboardController, mojom::LocaleUpdateController,
-                  mojom::LoginScreen, mojom::MediaController,
-                  mojom::NightLightController, mojom::NoteTakingController,
-                  mojom::ShutdownController, mojom::SystemTray,
-                  mojom::TabletModeController, mojom::TrayAction,
-                  mojom::VoiceInteractionController, mojom::VpnList,
-                  mojom::WallpaperController>())
+                  mojom::ImeController, mojom::KeyboardController,
+                  mojom::LocaleUpdateController, mojom::LoginScreen,
+                  mojom::MediaController, mojom::NightLightController,
+                  mojom::NoteTakingController, mojom::ShutdownController,
+                  mojom::SystemTray, mojom::TabletModeController,
+                  mojom::TrayAction, mojom::VoiceInteractionController,
+                  mojom::VpnList, mojom::WallpaperController>())
           .ExposeCapability("test", service_manager::Manifest::InterfaceList<
                                         mojom::ShelfIntegrationTestApi>())
           .RequireCapability("*", "accessibility")
diff --git a/ash/public/cpp/pagination/pagination_controller.h b/ash/public/cpp/pagination/pagination_controller.h
index 5bca602..aea9b2e 100644
--- a/ash/public/cpp/pagination/pagination_controller.h
+++ b/ash/public/cpp/pagination/pagination_controller.h
@@ -6,6 +6,7 @@
 #define ASH_PUBLIC_CPP_PAGINATION_PAGINATION_CONTROLLER_H_
 
 #include "ash/public/cpp/ash_public_export.h"
+#include "base/callback.h"
 #include "base/macros.h"
 #include "ui/events/event.h"
 #include "ui/events/event_constants.h"
diff --git a/ash/public/cpp/system_tray.cc b/ash/public/cpp/system_tray.cc
new file mode 100644
index 0000000..64f75b6
--- /dev/null
+++ b/ash/public/cpp/system_tray.cc
@@ -0,0 +1,32 @@
+// 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/public/cpp/system_tray.h"
+
+#include "base/logging.h"
+
+namespace ash {
+
+namespace {
+
+SystemTray* g_instance = nullptr;
+
+}  // namespace
+
+// static
+SystemTray* SystemTray::Get() {
+  return g_instance;
+}
+
+SystemTray::SystemTray() {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
+
+SystemTray::~SystemTray() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
+
+}  // namespace ash
diff --git a/ash/public/cpp/system_tray.h b/ash/public/cpp/system_tray.h
new file mode 100644
index 0000000..01547dd
--- /dev/null
+++ b/ash/public/cpp/system_tray.h
@@ -0,0 +1,31 @@
+// 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_PUBLIC_CPP_SYSTEM_TRAY_H_
+#define ASH_PUBLIC_CPP_SYSTEM_TRAY_H_
+
+#include "ash/public/cpp/ash_public_export.h"
+
+namespace ash {
+
+class SystemTrayClient;
+
+// Public interface to control the system tray bubble in ash.
+class ASH_PUBLIC_EXPORT SystemTray {
+ public:
+  static SystemTray* Get();
+
+  // Sets the client interface in the browser.
+  virtual void SetClient(SystemTrayClient* client) = 0;
+
+  // TODO(jamescook): Migrate ash::mojom::SystemTray here.
+
+ protected:
+  SystemTray();
+  virtual ~SystemTray();
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_SYSTEM_TRAY_H_
diff --git a/ash/public/cpp/system_tray_client.h b/ash/public/cpp/system_tray_client.h
new file mode 100644
index 0000000..9d2bda3
--- /dev/null
+++ b/ash/public/cpp/system_tray_client.h
@@ -0,0 +1,121 @@
+// 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_PUBLIC_CPP_SYSTEM_TRAY_CLIENT_H_
+#define ASH_PUBLIC_CPP_SYSTEM_TRAY_CLIENT_H_
+
+#include <string>
+
+#include "ash/public/cpp/ash_public_export.h"
+#include "base/strings/string16.h"
+
+namespace ash {
+
+class SystemTrayClient;
+
+// Handles method calls delegated back to chrome from ash.
+class ASH_PUBLIC_EXPORT SystemTrayClient {
+ public:
+  virtual ~SystemTrayClient() {}
+
+  // Shows general settings UI.
+  virtual void ShowSettings() = 0;
+
+  // Shows settings related to Bluetooth devices (e.g. to add a device).
+  virtual void ShowBluetoothSettings() = 0;
+
+  // Shows the web UI dialog to pair a Bluetooth device.
+  // |address| is the unique device address in the form "XX:XX:XX:XX:XX:XX"
+  // with hex digits X. |name_for_display| is a human-readable name, not
+  // necessarily the device name.
+  virtual void ShowBluetoothPairingDialog(
+      const std::string& address,
+      const base::string16& name_for_display,
+      bool paired,
+      bool connected) = 0;
+
+  // Shows the settings related to date, timezone etc.
+  virtual void ShowDateSettings() = 0;
+
+  // Shows the dialog to set system time, date, and timezone.
+  virtual void ShowSetTimeDialog() = 0;
+
+  // Shows settings related to multiple displays.
+  virtual void ShowDisplaySettings() = 0;
+
+  // Shows settings related to power.
+  virtual void ShowPowerSettings() = 0;
+
+  // Shows the page that lets you disable performance tracing.
+  virtual void ShowChromeSlow() = 0;
+
+  // Shows settings related to input methods.
+  virtual void ShowIMESettings() = 0;
+
+  // Shows settings related to MultiDevice features.
+  virtual void ShowConnectedDevicesSettings() = 0;
+
+  // Shows the about chrome OS page and checks for updates after the page is
+  // loaded.
+  virtual void ShowAboutChromeOS() = 0;
+
+  // Shows the Chromebook help app.
+  virtual void ShowHelp() = 0;
+
+  // Shows accessibility help.
+  virtual void ShowAccessibilityHelp() = 0;
+
+  // Shows the settings related to accessibility.
+  virtual void ShowAccessibilitySettings() = 0;
+
+  // Shows the help center article for the stylus tool palette.
+  virtual void ShowPaletteHelp() = 0;
+
+  // Shows the settings related to the stylus tool palette.
+  virtual void ShowPaletteSettings() = 0;
+
+  // Shows information about public account mode.
+  virtual void ShowPublicAccountInfo() = 0;
+
+  // Shows information about enterprise enrolled devices.
+  virtual void ShowEnterpriseInfo() = 0;
+
+  // Shows UI to configure or activate the network specified by |network_id|,
+  // which may include showing payment or captive portal UI when appropriate.
+  virtual void ShowNetworkConfigure(const std::string& network_id) = 0;
+
+  // Shows UI to create a new network connection. |type| is the ONC network type
+  // (see onc_spec.md). TODO(stevenjb): Use NetworkType from onc.mojo (TBD).
+  virtual void ShowNetworkCreate(const std::string& type) = 0;
+
+  // Shows the "add network" UI to create a third-party extension-backed VPN
+  // connection (e.g. Cisco AnyConnect).
+  virtual void ShowThirdPartyVpnCreate(const std::string& extension_id) = 0;
+
+  // Launches Arc VPN provider.
+  virtual void ShowArcVpnCreate(const std::string& app_id) = 0;
+
+  // Shows settings related to networking. If |network_id| is empty, shows
+  // general settings. Otherwise shows settings for the individual network.
+  // On devices |network_id| is a GUID, but on Linux desktop and in tests it can
+  // be any string.
+  virtual void ShowNetworkSettings(const std::string& network_id) = 0;
+
+  // Shows the MultiDevice setup flow dialog.
+  virtual void ShowMultiDeviceSetup() = 0;
+
+  // Attempts to restart the system for update.
+  virtual void RequestRestartForUpdate() = 0;
+
+  // Sets the UI locale to |locale_iso_code| and exit the session to take
+  // effect.
+  virtual void SetLocaleAndExit(const std::string& locale_iso_code) = 0;
+
+ protected:
+  SystemTrayClient() {}
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_SYSTEM_TRAY_CLIENT_H_
diff --git a/ash/public/interfaces/accessibility_controller.mojom b/ash/public/interfaces/accessibility_controller.mojom
index 6de0721..222ba80 100644
--- a/ash/public/interfaces/accessibility_controller.mojom
+++ b/ash/public/interfaces/accessibility_controller.mojom
@@ -53,6 +53,10 @@
   // Setting off-screen or empty bounds suppresses the highlight.
   SetCaretBounds(gfx.mojom.Rect bounds_in_screen);
 
+  // Sets whether the accessibility panel should always be visible, regardless
+  // of whether the window is fullscreen.
+  SetAccessibilityPanelAlwaysVisible(bool always_visible);
+
   // Sets the bounds for the accessibility panel. Overrides current
   // configuration (i.e. fullscreen, full-width).
   SetAccessibilityPanelBounds(gfx.mojom.Rect bounds,
diff --git a/ash/public/interfaces/system_tray.mojom b/ash/public/interfaces/system_tray.mojom
index e517a32..9874221 100644
--- a/ash/public/interfaces/system_tray.mojom
+++ b/ash/public/interfaces/system_tray.mojom
@@ -18,9 +18,6 @@
 
 // Allows clients (e.g. Chrome browser) to control the ash system tray menu.
 interface SystemTray {
-  // Sets the client interface.
-  SetClient(SystemTrayClient client);
-
   // Sets the enabled state of the tray on the primary display. If not |enabled|
   // any open menu will be closed.
   SetPrimaryTrayEnabled(bool enabled);
@@ -88,99 +85,3 @@
   // Shows the volume slider bubble shown at the right bottom of screen.
   ShowVolumeSliderBubble();
 };
-
-// Allows ash system tray to control a client (e.g. Chrome browser). Requests
-// often involve preferences or web UI that is not available to ash.
-interface SystemTrayClient {
-  // Shows general settings UI.
-  ShowSettings();
-
-  // Shows settings related to Bluetooth devices (e.g. to add a device).
-  ShowBluetoothSettings();
-
-  // Shows the web UI dialog to pair a Bluetooth device.
-  // |address| is the unique device address in the form "XX:XX:XX:XX:XX:XX"
-  // with hex digits X. |name_for_display| is a human-readable name, not
-  // necessarily the device name.
-  ShowBluetoothPairingDialog(string address,
-                             mojo_base.mojom.String16 name_for_display,
-                             bool paired,
-                             bool connected);
-
-  // Shows the settings related to date, timezone etc.
-  ShowDateSettings();
-
-  // Shows the dialog to set system time, date, and timezone.
-  ShowSetTimeDialog();
-
-  // Shows settings related to multiple displays.
-  ShowDisplaySettings();
-
-  // Shows settings related to power.
-  ShowPowerSettings();
-
-  // Shows the page that lets you disable performance tracing.
-  ShowChromeSlow();
-
-  // Shows settings related to input methods.
-  ShowIMESettings();
-
-  // Shows settings related to MultiDevice features.
-  ShowConnectedDevicesSettings();
-
-  // Shows the about chrome OS page and checks for updates after the page is
-  // loaded.
-  ShowAboutChromeOS();
-
-  // Shows the Chromebook help app.
-  ShowHelp();
-
-  // Shows accessibility help.
-  ShowAccessibilityHelp();
-
-  // Shows the settings related to accessibility.
-  ShowAccessibilitySettings();
-
-  // Shows the help center article for the stylus tool palette.
-  ShowPaletteHelp();
-
-  // Shows the settings related to the stylus tool palette.
-  ShowPaletteSettings();
-
-  // Shows information about public account mode.
-  ShowPublicAccountInfo();
-
-  // Shows information about enterprise enrolled devices.
-  ShowEnterpriseInfo();
-
-  // Shows UI to configure or activate the network specified by |network_id|,
-  // which may include showing payment or captive portal UI when appropriate.
-  ShowNetworkConfigure(string network_id);
-
-  // Shows UI to create a new network connection. |type| is the ONC network type
-  // (see onc_spec.md). TODO(stevenjb): Use NetworkType from onc.mojo (TBD).
-  ShowNetworkCreate(string type);
-
-  // Shows the "add network" UI to create a third-party extension-backed VPN
-  // connection (e.g. Cisco AnyConnect).
-  ShowThirdPartyVpnCreate(string extension_id);
-
-  // Launches Arc VPN provider.
-  ShowArcVpnCreate(string app_id);
-
-  // Shows settings related to networking. If |network_id| is empty, shows
-  // general settings. Otherwise shows settings for the individual network.
-  // On devices |network_id| is a GUID, but on Linux desktop and in tests it can
-  // be any string.
-  ShowNetworkSettings(string network_id);
-
-  // Shows the MultiDevice setup flow dialog.
-  ShowMultiDeviceSetup();
-
-  // Attempts to restart the system for update.
-  RequestRestartForUpdate();
-
-  // Sets the UI locale to |locale_iso_code| and exit the session to take
-  // effect.
-  SetLocaleAndExit(string locale_iso_code);
-};
diff --git a/ash/root_window_controller.cc b/ash/root_window_controller.cc
index 33295f2c..8e6873d8 100644
--- a/ash/root_window_controller.cc
+++ b/ash/root_window_controller.cc
@@ -1062,6 +1062,16 @@
   settings_bubble_container->SetProperty(::wm::kUsesScreenCoordinatesKey, true);
   settings_bubble_container->SetProperty(kLockedToRootKey, true);
 
+  aura::Window* accessibility_panel_container = CreateContainer(
+      kShellWindowId_AccessibilityPanelContainer, "AccessibilityPanelContainer",
+      lock_screen_related_containers);
+  ::wm::SetChildWindowVisibilityChangesAnimated(accessibility_panel_container);
+  accessibility_panel_container->SetProperty(::wm::kUsesScreenCoordinatesKey,
+                                             true);
+  accessibility_panel_container->SetProperty(kLockedToRootKey, true);
+  accessibility_panel_container->SetLayoutManager(
+      new AccessibilityPanelLayoutManager());
+
   aura::Window* virtual_keyboard_parent_container = CreateContainer(
       kShellWindowId_ImeWindowParentContainer, "ImeWindowParentContainer",
       lock_screen_related_containers);
@@ -1094,16 +1104,6 @@
   autoclick_container->SetProperty(::wm::kUsesScreenCoordinatesKey, true);
   wm::SetSnapsChildrenToPhysicalPixelBoundary(autoclick_container);
 
-  aura::Window* accessibility_panel_container = CreateContainer(
-      kShellWindowId_AccessibilityPanelContainer, "AccessibilityPanelContainer",
-      lock_screen_related_containers);
-  ::wm::SetChildWindowVisibilityChangesAnimated(accessibility_panel_container);
-  accessibility_panel_container->SetProperty(::wm::kUsesScreenCoordinatesKey,
-                                             true);
-  accessibility_panel_container->SetProperty(kLockedToRootKey, true);
-  accessibility_panel_container->SetLayoutManager(
-      new AccessibilityPanelLayoutManager());
-
   aura::Window* drag_drop_container = CreateContainer(
       kShellWindowId_DragImageAndTooltipContainer,
       "DragImageAndTooltipContainer", lock_screen_related_containers);
diff --git a/ash/session/test_session_controller_client.cc b/ash/session/test_session_controller_client.cc
index b9b396a4..07894f2 100644
--- a/ash/session/test_session_controller_client.cc
+++ b/ash/session/test_session_controller_client.cc
@@ -10,6 +10,7 @@
 #include "ash/login_status.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/session/test_pref_service_provider.h"
+#include "ash/shell.h"
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/run_loop.h"
@@ -19,6 +20,7 @@
 #include "components/prefs/pref_service.h"
 #include "components/session_manager/session_manager_types.h"
 #include "components/user_manager/user_type.h"
+#include "ui/views/widget/widget.h"
 
 namespace ash {
 
@@ -209,18 +211,9 @@
 
 void TestSessionControllerClient::SwitchActiveUser(
     const AccountId& account_id) {
-  std::vector<uint32_t> session_order;
-  session_order.reserve(controller_->GetUserSessions().size());
-
-  for (const auto& user_session : controller_->GetUserSessions()) {
-    if (user_session->user_info.account_id == account_id) {
-      session_order.insert(session_order.begin(), user_session->session_id);
-    } else {
-      session_order.push_back(user_session->session_id);
-    }
-  }
-
-  controller_->SetUserSessionOrder(session_order);
+  controller_->CanSwitchActiveUser(
+      base::BindOnce(&TestSessionControllerClient::DoSwitchUser,
+                     weak_ptr_factory_.GetWeakPtr(), account_id));
 }
 
 void TestSessionControllerClient::CycleActiveUser(
@@ -265,7 +258,16 @@
   SwitchActiveUser((*it)->user_info.account_id);
 }
 
-void TestSessionControllerClient::ShowMultiProfileLogin() {}
+void TestSessionControllerClient::ShowMultiProfileLogin() {
+  views::Widget::InitParams params;
+  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+  params.bounds = gfx::Rect(0, 0, 400, 300);
+  params.context = Shell::GetPrimaryRootWindow();
+
+  multi_profile_login_widget_ = std::make_unique<views::Widget>();
+  multi_profile_login_widget_->Init(params);
+  multi_profile_login_widget_->Show();
+}
 
 void TestSessionControllerClient::EmitAshInitialized() {}
 
@@ -278,4 +280,23 @@
   return prefs_provider_ ? prefs_provider_->GetUserPrefs(account_id) : nullptr;
 }
 
+void TestSessionControllerClient::DoSwitchUser(const AccountId& account_id,
+                                               bool switch_user) {
+  if (!switch_user)
+    return;
+
+  std::vector<uint32_t> session_order;
+  session_order.reserve(controller_->GetUserSessions().size());
+
+  for (const auto& user_session : controller_->GetUserSessions()) {
+    if (user_session->user_info.account_id == account_id) {
+      session_order.insert(session_order.begin(), user_session->session_id);
+    } else {
+      session_order.push_back(user_session->session_id);
+    }
+  }
+
+  controller_->SetUserSessionOrder(session_order);
+}
+
 }  // namespace ash
diff --git a/ash/session/test_session_controller_client.h b/ash/session/test_session_controller_client.h
index 5ee5c8a..e2accf3 100644
--- a/ash/session/test_session_controller_client.h
+++ b/ash/session/test_session_controller_client.h
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 
+#include <memory>
 #include <string>
 
 #include "ash/public/cpp/session/session_controller_client.h"
@@ -17,6 +18,10 @@
 #include "base/token.h"
 #include "components/user_manager/user_type.h"
 
+namespace views {
+class Widget;
+}
+
 class AccountId;
 class PrefService;
 
@@ -114,6 +119,8 @@
   PrefService* GetUserPrefService(const AccountId& account_id) override;
 
  private:
+  void DoSwitchUser(const AccountId& account_id, bool switch_user);
+
   SessionControllerImpl* const controller_;
   TestPrefServiceProvider* const prefs_provider_;
 
@@ -123,6 +130,8 @@
   bool use_lower_case_user_id_ = true;
   int request_sign_out_count_ = 0;
 
+  std::unique_ptr<views::Widget> multi_profile_login_widget_;
+
   base::WeakPtrFactory<TestSessionControllerClient> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(TestSessionControllerClient);
diff --git a/ash/shelf/kiosk_next_shelf_view.cc b/ash/shelf/kiosk_next_shelf_view.cc
index 529066b..2cdd730 100644
--- a/ash/shelf/kiosk_next_shelf_view.cc
+++ b/ash/shelf/kiosk_next_shelf_view.cc
@@ -59,7 +59,7 @@
   set_last_visible_index(model()->item_count() - 1);
 }
 
-void KioskNextShelfView::CalculateIdealBounds() const {
+void KioskNextShelfView::CalculateIdealBounds() {
   DCHECK(shelf()->IsHorizontalAlignment());
   DCHECK_EQ(2, model()->item_count());
 
diff --git a/ash/shelf/kiosk_next_shelf_view.h b/ash/shelf/kiosk_next_shelf_view.h
index 8f028ae..710ba3d 100644
--- a/ash/shelf/kiosk_next_shelf_view.h
+++ b/ash/shelf/kiosk_next_shelf_view.h
@@ -33,9 +33,7 @@
 
  private:
   // ShelfView:
-  // This method is not really const, because it updates properties of the class
-  // members. The problem comes from parent class that violates constness.
-  void CalculateIdealBounds() const override;
+  void CalculateIdealBounds() override;
   void LayoutAppListAndBackButtonHighlight() override;
 
   DISALLOW_COPY_AND_ASSIGN(KioskNextShelfView);
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index 2040886..bdfbfe3 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -1308,6 +1308,11 @@
         0);
   }
 
+  // Do not perform any checks based on the cursor position if the mouse cursor
+  // is currently hidden.
+  if (!shelf_widget_->IsMouseEventsEnabled())
+    return SHELF_AUTO_HIDE_HIDDEN;
+
   gfx::Point cursor_position_in_screen =
       display::Screen::GetScreen()->GetCursorScreenPoint();
   // Cursor is invisible in tablet mode and plug in an external mouse in tablet
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc
index 73ebd5b..dcce7bf 100644
--- a/ash/shelf/shelf_layout_manager_unittest.cc
+++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -1132,6 +1132,23 @@
   EXPECT_EQ(stable_work_area,
             GetPrimaryWorkAreaInsets()->ComputeStableWorkArea());
 
+  // Move the mouse to the bottom again to show the shelf.
+  generator->MoveMouseTo(0, display_bottom - 1);
+  UpdateAutoHideStateNow();
+  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
+
+  // A tap on the maximized window should hide the shelf, even if the most
+  // recent mouse position was over the shelf (crbug.com/963977).
+  EXPECT_TRUE(widget->IsMouseEventsEnabled());
+  gfx::Rect window_bounds = widget->GetNativeWindow()->GetBoundsInScreen();
+  generator->GestureTapAt(window_bounds.origin() + gfx::Vector2d(10, 10));
+  EXPECT_FALSE(widget->IsMouseEventsEnabled());
+  UpdateAutoHideStateNow();
+  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
+
+  // Return the mouse to the top.
+  generator->MoveMouseTo(0, 0);
+
   // Drag mouse to bottom of screen.
   generator->PressLeftButton();
   generator->MoveMouseTo(0, display_bottom - 1);
diff --git a/ash/shelf/shelf_view.cc b/ash/shelf/shelf_view.cc
index 156948a..98b4d55 100644
--- a/ash/shelf/shelf_view.cc
+++ b/ash/shelf/shelf_view.cc
@@ -548,7 +548,9 @@
 }
 
 gfx::Size ShelfView::CalculatePreferredSize() const {
-  CalculateIdealBounds();
+  // Use |last_visible_index_| != -1 as sentinel to ensure
+  // CalculateIdealBounds() is called before getting here.
+  DCHECK_NE(last_visible_index_, -1);
 
   int last_button_index = last_visible_index_;
   if (!is_overflow_mode() && overflow_button_ && overflow_button_->GetVisible())
@@ -915,7 +917,7 @@
   }
 }
 
-void ShelfView::CalculateIdealBounds() const {
+void ShelfView::CalculateIdealBounds() {
   DCHECK(model_->item_count() == view_model_->view_size());
 
   const int button_spacing = ShelfConstants::button_spacing();
@@ -1366,8 +1368,7 @@
          2 * kAppIconGroupMargin;
 }
 
-ShelfView::AppCenteringStrategy ShelfView::CalculateAppCenteringStrategy()
-    const {
+ShelfView::AppCenteringStrategy ShelfView::CalculateAppCenteringStrategy() {
   // There are two possibilities. Either all the apps fit when centered
   // on the whole screen width, in which case we do that. Or, when space
   // becomes a little tight (which happens especially when the status area
diff --git a/ash/shelf/shelf_view.h b/ash/shelf/shelf_view.h
index 7a2b95b..ad3c561 100644
--- a/ash/shelf/shelf_view.h
+++ b/ash/shelf/shelf_view.h
@@ -330,7 +330,7 @@
  protected:
   // Calculates the ideal bounds. The bounds of each button corresponding to an
   // item in the model is set in |view_model_|.
-  virtual void CalculateIdealBounds() const;
+  virtual void CalculateIdealBounds();
 
   virtual void LayoutAppListAndBackButtonHighlight();
 
@@ -342,11 +342,6 @@
     return back_and_app_list_background_;
   }
 
-  // view_model() is called from const method CalculateIdealBounds() that
-  // modifies it. This is needed to work around ShelfView's problem with const
-  // correctness around CalculateIdealBounds() method.
-  views::ViewModel* view_model() const { return view_model_.get(); }
-
   void set_first_visible_index(int index) { first_visible_index_ = index; }
   void set_last_visible_index(int index) { last_visible_index_ = index; }
 
@@ -387,7 +382,7 @@
   // This method determines which centering strategy is adequate, returns that,
   // and sets the |first_visible_index_| and |last_visible_index_| fields
   // appropriately.
-  AppCenteringStrategy CalculateAppCenteringStrategy() const;
+  AppCenteringStrategy CalculateAppCenteringStrategy();
   void LayoutOverflowButton() const;
 
   // Returns the index of the last view whose max primary axis coordinate is
@@ -575,11 +570,11 @@
   // * 1 (app list button) for the main shelf when tablet mode is off
   // * > 1 when this shelf view is the overflow shelf view and only shows a
   //   subset of items.
-  mutable int first_visible_index_ = 0;
+  int first_visible_index_ = 0;
 
   // Last index of a launcher button that is visible (does not go into
   // overflow).
-  mutable int last_visible_index_ = -1;
+  int last_visible_index_ = -1;
 
   std::unique_ptr<views::BoundsAnimator> bounds_animator_;
 
diff --git a/ash/shell.cc b/ash/shell.cc
index 009c6e2c..a3c8640 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -53,7 +53,6 @@
 #include "ash/home_screen/home_screen_controller.h"
 #include "ash/host/ash_window_tree_host_init_params.h"
 #include "ash/ime/ime_controller.h"
-#include "ash/ime/ime_engine_factory_registry.h"
 #include "ash/keyboard/ash_keyboard_controller.h"
 #include "ash/keyboard/ui/keyboard_ui_factory.h"
 #include "ash/kiosk_next/kiosk_next_shell_controller.h"
@@ -551,8 +550,6 @@
       connector_(connector),
       focus_cycler_(std::make_unique<FocusCycler>()),
       ime_controller_(std::make_unique<ImeController>()),
-      ime_engine_factory_registry_(
-          std::make_unique<ImeEngineFactoryRegistry>()),
       immersive_context_(std::make_unique<ImmersiveContextAsh>()),
       keyboard_brightness_control_delegate_(
           std::make_unique<KeyboardBrightnessController>()),
@@ -773,7 +770,6 @@
   accessibility_focus_ring_controller_.reset();
   policy_recommendation_restorer_.reset();
   ime_controller_.reset();
-  ime_engine_factory_registry_.reset();
 
   // Balances the Install() in Initialize().
   views::FocusManagerFactory::Install(nullptr);
diff --git a/ash/shell.h b/ash/shell.h
index 59ede42..b80352d 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -124,7 +124,6 @@
 class HighlighterController;
 class HomeScreenController;
 class ImeController;
-class ImeEngineFactoryRegistry;
 class ImmersiveContext;
 class KeyAccessibilityEnabler;
 class KeyboardBrightnessControlDelegate;
@@ -374,9 +373,6 @@
     return high_contrast_controller_.get();
   }
   ImeController* ime_controller() { return ime_controller_.get(); }
-  ImeEngineFactoryRegistry* ime_engine_factory_registry() {
-    return ime_engine_factory_registry_.get();
-  }
   KeyAccessibilityEnabler* key_accessibility_enabler() {
     return key_accessibility_enabler_.get();
   }
@@ -669,7 +665,6 @@
   std::unique_ptr<FocusCycler> focus_cycler_;
   std::unique_ptr<HomeScreenController> home_screen_controller_;
   std::unique_ptr<ImeController> ime_controller_;
-  std::unique_ptr<ImeEngineFactoryRegistry> ime_engine_factory_registry_;
   std::unique_ptr<ImmersiveContext> immersive_context_;
   std::unique_ptr<KeyboardBrightnessControlDelegate>
       keyboard_brightness_control_delegate_;
diff --git a/ash/strings/ash_strings_ar.xtb b/ash/strings/ash_strings_ar.xtb
index e4e3405..99de5a5 100644
--- a/ash/strings/ash_strings_ar.xtb
+++ b/ash/strings/ash_strings_ar.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101">سيظل بإمكان <ph name="USER_EMAIL_ADDRESS" /> تسجيل الدخول لاحقًا.</translation>
 <translation id="3321628682574733415">رمز أحد الوالدين غير صحيح</translation>
 <translation id="332587331255250389">يُرجى استبدال البطارية</translation>
+<translation id="3335825575923019462">هل أنت متأكد من أنك تريد إيقاف النقرات التلقائية؟</translation>
 <translation id="3351879221545518001">أنت حاليًا تبث محتوى الشاشة.</translation>
 <translation id="3364721542077212959">أدوات قلم الشاشة</translation>
 <translation id="3368922792935385530">متصل</translation>
@@ -400,6 +401,7 @@
 <translation id="7348093485538360975">لوحة المفاتيح على الشاشة</translation>
 <translation id="735745346212279324">تم قطع اتصال الشبكة الافتراضية الخاصة</translation>
 <translation id="7377169924702866686">‏مفتاح Caps Lock في وضع التشغيل.</translation>
+<translation id="7378594059915113390">عناصر التحكم في الوسائط</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> من الثواني</translation>
 <translation id="7398254312354928459">تم تبديل اتصال الشبكة</translation>
 <translation id="7405710164030118432">‏لفتح قفل الجهاز، يُرجى إدخال رمز الدخول الخاص بالوالدين في Family Link.</translation>
@@ -485,6 +487,7 @@
 <translation id="8649101189709089199">الاختيار والاستماع</translation>
 <translation id="8652175077544655965">غلق الإعدادات</translation>
 <translation id="8653151467777939995">عرض إعدادات الإشعارات. تم تفعيل الإشعارات</translation>
+<translation id="8664483332071009680">هل تريد إيقاف النقرات التلقائية؟</translation>
 <translation id="8664753092453405566">عرض قائمة الشبكات. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270 درجة</translation>
 <translation id="8676770494376880701">تمّ توصيل شاحن منخفض الطاقة</translation>
diff --git a/ash/strings/ash_strings_bg.xtb b/ash/strings/ash_strings_bg.xtb
index 3121f1f..7bb4ef8 100644
--- a/ash/strings/ash_strings_bg.xtb
+++ b/ash/strings/ash_strings_bg.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> пак може да влезе в профила си по-късно.</translation>
 <translation id="3321628682574733415">Неправилен код на родител</translation>
 <translation id="332587331255250389">Моля, заменете батерията</translation>
+<translation id="3335825575923019462">Наистина ли искате да деактивирате автоматичните кликвания?</translation>
 <translation id="3351879221545518001">Понастоящем предавате екрана.</translation>
 <translation id="3364721542077212959">Инструменти за писане</translation>
 <translation id="3368922792935385530">Установена е връзка</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">Екранна клавиатура</translation>
 <translation id="735745346212279324">Връзката с виртуалната частна мрежа (VPN) е прекъсната</translation>
 <translation id="7377169924702866686">„Caps Lock“ е включен.</translation>
+<translation id="7378594059915113390">Контроли за мултимедия</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> сек</translation>
 <translation id="7398254312354928459">Преминахте към друга мрежа</translation>
 <translation id="7405710164030118432">За да отключите устройството, въведете кода си за достъп на родител от Family Link</translation>
@@ -484,6 +486,7 @@
 <translation id="8649101189709089199">Прочитане на глас</translation>
 <translation id="8652175077544655965">Затваряне на настройките</translation>
 <translation id="8653151467777939995">Показване на настройките за известия. Известията са включени</translation>
+<translation id="8664483332071009680">Искате ли да деактивирате автоматичните кликвания?</translation>
 <translation id="8664753092453405566">Показване на списъка с мрежи. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Свързано е зарядно устройство с малка мощност</translation>
diff --git a/ash/strings/ash_strings_ca.xtb b/ash/strings/ash_strings_ca.xtb
index f060b46..32af0ec 100644
--- a/ash/strings/ash_strings_ca.xtb
+++ b/ash/strings/ash_strings_ca.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> pot iniciar la sessió més tard.</translation>
 <translation id="3321628682574733415">Codi parental incorrecte</translation>
 <translation id="332587331255250389">Canvia la bateria</translation>
+<translation id="3335825575923019462">Confirmes que vols desactivar els clics automàtics?</translation>
 <translation id="3351879221545518001">Estàs emetent la pantalla.</translation>
 <translation id="3364721542077212959">Eines del llapis òptic</translation>
 <translation id="3368922792935385530">Connectat</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">Teclat en pantalla</translation>
 <translation id="735745346212279324">VPN desconnectada</translation>
 <translation id="7377169924702866686">Bloq Maj està activat.</translation>
+<translation id="7378594059915113390">Controls multimèdia</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> s</translation>
 <translation id="7398254312354928459">S'ha canviat la connexió de xarxa</translation>
 <translation id="7405710164030118432">Introdueix el codi d'accés parental de Family Link per desbloquejar el dispositiu</translation>
@@ -484,6 +486,7 @@
 <translation id="8649101189709089199">Escolta la selecció</translation>
 <translation id="8652175077544655965">Tanca la configuració</translation>
 <translation id="8653151467777939995">Mostra la configuració de notificacions. Les notificacions estan activades.</translation>
+<translation id="8664483332071009680">Vols desactivar els clics automàtics?</translation>
 <translation id="8664753092453405566">Mostra la llista de xarxes. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">S'ha connectat un carregador de baix consum</translation>
diff --git a/ash/strings/ash_strings_da.xtb b/ash/strings/ash_strings_da.xtb
index a939454..0dd81fe 100644
--- a/ash/strings/ash_strings_da.xtb
+++ b/ash/strings/ash_strings_da.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> kan stadig logge ind senere.</translation>
 <translation id="3321628682574733415">Forkert forældrekode</translation>
 <translation id="332587331255250389">Udskift batteriet</translation>
+<translation id="3335825575923019462">Er du sikker på, at du vil deaktivere automatiske klik?</translation>
 <translation id="3351879221545518001">Du caster i øjeblikket skærmen.</translation>
 <translation id="3364721542077212959">Styluspenværktøjer</translation>
 <translation id="3368922792935385530">Tilsluttet</translation>
@@ -400,6 +401,7 @@
 <translation id="7348093485538360975">Skærmtastatur</translation>
 <translation id="735745346212279324">VPN afbrudt</translation>
 <translation id="7377169924702866686">Caps Lock er slået til.</translation>
+<translation id="7378594059915113390">Mediestyring</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" />sek.</translation>
 <translation id="7398254312354928459">Der blev skiftet netværksforbindelse</translation>
 <translation id="7405710164030118432">Angiv forældreadgangskoden til Family Link for at låse enheden op</translation>
@@ -485,6 +487,7 @@
 <translation id="8649101189709089199">Tekstoplæsning</translation>
 <translation id="8652175077544655965">Luk indstillinger</translation>
 <translation id="8653151467777939995">Vis indstillinger for notifikationer. Notifikationer er slået til</translation>
+<translation id="8664483332071009680">Vil du deaktivere automatiske klik?</translation>
 <translation id="8664753092453405566">Vis netværksliste. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Oplader med lav kraft er tilsluttet</translation>
diff --git a/ash/strings/ash_strings_de.xtb b/ash/strings/ash_strings_de.xtb
index c5d84d64..4c642f9 100644
--- a/ash/strings/ash_strings_de.xtb
+++ b/ash/strings/ash_strings_de.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> kann sich später weiterhin anmelden.</translation>
 <translation id="3321628682574733415">Falscher Elterncode</translation>
 <translation id="332587331255250389">Bitte tauschen Sie die Batterien aus</translation>
+<translation id="3335825575923019462">Möchten Sie automatische Klicks wirklich deaktivieren?</translation>
 <translation id="3351879221545518001">Sie streamen gerade den Bildschirm.</translation>
 <translation id="3364721542077212959">Eingabestift-Tools</translation>
 <translation id="3368922792935385530">Verbunden</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">Bildschirmtastatur</translation>
 <translation id="735745346212279324">VPN-Verbindung getrennt</translation>
 <translation id="7377169924702866686">Feststelltaste An</translation>
+<translation id="7378594059915113390">Mediensteuerelemente</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> Sek.</translation>
 <translation id="7398254312354928459">Netzwerkverbindung gewechselt</translation>
 <translation id="7405710164030118432">Wenn Sie das Gerät entsperren möchten, geben Sie Ihren Eltern-Zugangscode für Family Link ein</translation>
@@ -484,6 +486,7 @@
 <translation id="8649101189709089199">Vorlesen</translation>
 <translation id="8652175077544655965">Einstellungen schließen</translation>
 <translation id="8653151467777939995">Benachrichtigungseinstellungen anzeigen. Benachrichtigungen sind aktiviert.</translation>
+<translation id="8664483332071009680">Automatische Klicks deaktivieren?</translation>
 <translation id="8664753092453405566">Netzwerkliste anzeigen. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Schwachstrom-Ladegerät angeschlossen</translation>
diff --git a/ash/strings/ash_strings_el.xtb b/ash/strings/ash_strings_el.xtb
index a052f44..a8cd163 100644
--- a/ash/strings/ash_strings_el.xtb
+++ b/ash/strings/ash_strings_el.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101">Ο χρήστης <ph name="USER_EMAIL_ADDRESS" /> μπορεί να συνδεθεί αργότερα.</translation>
 <translation id="3321628682574733415">Λανθασμένος κωδικός γονέα</translation>
 <translation id="332587331255250389">Αντικαταστήστε την μπαταρία</translation>
+<translation id="3335825575923019462">Είστε βέβαιοι ότι θέλετε να απενεργοποιήσετε τα αυτόματα κλικ;</translation>
 <translation id="3351879221545518001">Αυτήν τη στιγμή πραγματοποιείτε μετάδοση της οθόνης.</translation>
 <translation id="3364721542077212959">Εργαλεία γραφίδας</translation>
 <translation id="3368922792935385530">Σε σύνδεση</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">Πληκτρολόγιο οθόνης</translation>
 <translation id="735745346212279324">Το VPN αποσυνδέθηκε</translation>
 <translation id="7377169924702866686">Το Caps Lock είναι ενεργοποιημένο.</translation>
+<translation id="7378594059915113390">Στοιχεία ελέγχου μέσων</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> δ.</translation>
 <translation id="7398254312354928459">Έγινε εναλλαγή της σύνδεσης δικτύου</translation>
 <translation id="7405710164030118432">Για να ξεκλειδώσετε τη συσκευή, εισαγάγετε τον κωδικό πρόσβασης γονέα στο Family Link</translation>
@@ -484,6 +486,7 @@
 <translation id="8649101189709089199">Επιλέξτε για αυτόματη ανάγνωση</translation>
 <translation id="8652175077544655965">Κλείσιμο ρυθμίσεων</translation>
 <translation id="8653151467777939995">Εμφάνιση ρυθμίσεων ειδοποιήσεων. Οι ειδοποιήσεις είναι ενεργοποιημένες.</translation>
+<translation id="8664483332071009680">Να απενεργοποιηθούν τα αυτόματα κλικ;</translation>
 <translation id="8664753092453405566">Εμφάνιση λίστας δικτύων. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Ο συνδεδεμένος φορτιστής παρέχει χαμηλή ισχύ</translation>
diff --git a/ash/strings/ash_strings_fa.xtb b/ash/strings/ash_strings_fa.xtb
index 7fc0112..f8bfd12 100644
--- a/ash/strings/ash_strings_fa.xtb
+++ b/ash/strings/ash_strings_fa.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> همچنان می‌تواند بعداً به سیستم وارد شود.</translation>
 <translation id="3321628682574733415">کد والدین نادرست است</translation>
 <translation id="332587331255250389">لطفاً باتری را تعویض کنید</translation>
+<translation id="3335825575923019462">مطمئنید می‌خواهید کلیک‌های خودکار را غیرفعال کنید؟</translation>
 <translation id="3351879221545518001">درحال ارسال محتوای صفحه هستید.</translation>
 <translation id="3364721542077212959">ابزارهای قلم</translation>
 <translation id="3368922792935385530">متصل</translation>
@@ -400,6 +401,7 @@
 <translation id="7348093485538360975">صفحه‌کلید روی صفحه</translation>
 <translation id="735745346212279324">‏VPN قطع است</translation>
 <translation id="7377169924702866686">‏Caps Lock روشن است.</translation>
+<translation id="7378594059915113390">کنترل‌های رسانه</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> ثانیه</translation>
 <translation id="7398254312354928459">اتصال شبکه تغییریافته</translation>
 <translation id="7405710164030118432">‏برای باز کردن قفل دستگاه، کد دسترسی والدین را برای Family Link وارد کنید</translation>
@@ -485,6 +487,7 @@
 <translation id="8649101189709089199">انتخاب برای شنیدن</translation>
 <translation id="8652175077544655965">بستن تنظیمات</translation>
 <translation id="8653151467777939995">نمایش تنظیمات اعلان. اعلان‌ها روشن است</translation>
+<translation id="8664483332071009680">کلیک‌های خودکار غیرفعال شود؟</translation>
 <translation id="8664753092453405566">نمایش فهرست شبکه. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">‎۲۷۰°‎</translation>
 <translation id="8676770494376880701">شارژر برق متصل شده ضعیف است</translation>
diff --git a/ash/strings/ash_strings_fi.xtb b/ash/strings/ash_strings_fi.xtb
index ca74047..bd2c125 100644
--- a/ash/strings/ash_strings_fi.xtb
+++ b/ash/strings/ash_strings_fi.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> voi silti kirjautua sisään myöhemmin.</translation>
 <translation id="3321628682574733415">Virheellinen vanhemman koodi</translation>
 <translation id="332587331255250389">Vaihda paristo.</translation>
+<translation id="3335825575923019462">Haluatko varmasti poistaa automaattiset klikkaukset käytöstä?</translation>
 <translation id="3351879221545518001">Suoratoistat tällä hetkellä näyttöä.</translation>
 <translation id="3364721542077212959">Näyttökynätyökalut</translation>
 <translation id="3368922792935385530">Yhdistetty</translation>
@@ -402,6 +403,7 @@
 <translation id="7348093485538360975">Virtuaalinäppäimistö</translation>
 <translation id="735745346212279324">VPN-yhteys katkaistu</translation>
 <translation id="7377169924702866686">Caps Lock on päällä.</translation>
+<translation id="7378594059915113390">Mediaohjaimet</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />. <ph name="SECONDS_REMAINING" /> s</translation>
 <translation id="7398254312354928459">Verkko vaihdettu</translation>
 <translation id="7405710164030118432">Avaa laitteen lukitus vanhemman Family Link ‑käyttökoodilla</translation>
@@ -487,6 +489,7 @@
 <translation id="8649101189709089199">Teksti puhuttuna</translation>
 <translation id="8652175077544655965">Sulje asetukset</translation>
 <translation id="8653151467777939995">Näytä ilmoitusasetukset. Ilmoitukset ovat käytössä.</translation>
+<translation id="8664483332071009680">Poistetaanko automaattiset klikkaukset käytöstä?</translation>
 <translation id="8664753092453405566">Näytä verkkoluettelo. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Pienitehoinen laturi kytketty</translation>
diff --git a/ash/strings/ash_strings_fr.xtb b/ash/strings/ash_strings_fr.xtb
index 009826f..83367eb 100644
--- a/ash/strings/ash_strings_fr.xtb
+++ b/ash/strings/ash_strings_fr.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> pourra toujours se connecter plus tard.</translation>
 <translation id="3321628682574733415">Code parental incorrect</translation>
 <translation id="332587331255250389">Veuillez remplacer la batterie</translation>
+<translation id="3335825575923019462">Voulez-vous vraiment désactiver les clics automatiques ?</translation>
 <translation id="3351879221545518001">Vous êtes actuellement en train de caster l'écran.</translation>
 <translation id="3364721542077212959">Outils de stylet</translation>
 <translation id="3368922792935385530">Connecté</translation>
@@ -400,6 +401,7 @@
 <translation id="7348093485538360975">Clavier virtuel</translation>
 <translation id="735745346212279324">VPN déconnecté</translation>
 <translation id="7377169924702866686">La touche de verrouillage des majuscules est activée</translation>
+<translation id="7378594059915113390">Commandes multimédias</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" /> min <ph name="SECONDS_REMAINING" /> s</translation>
 <translation id="7398254312354928459">La connexion réseau a été établie</translation>
 <translation id="7405710164030118432">Pour déverrouiller l'appareil, saisissez le code d'accès parental Family Link</translation>
@@ -485,6 +487,7 @@
 <translation id="8649101189709089199">Sélectionner pour prononcer</translation>
 <translation id="8652175077544655965">Fermer les paramètres</translation>
 <translation id="8653151467777939995">Afficher les paramètres de notification. Les notifications sont activées</translation>
+<translation id="8664483332071009680">Désactiver les clics automatiques ?</translation>
 <translation id="8664753092453405566">Afficher la liste des réseaux. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Chargeur de faible puissance connecté</translation>
diff --git a/ash/strings/ash_strings_hu.xtb b/ash/strings/ash_strings_hu.xtb
index 588516f..803cb4a 100644
--- a/ash/strings/ash_strings_hu.xtb
+++ b/ash/strings/ash_strings_hu.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> később be tud jelentkezni.</translation>
 <translation id="3321628682574733415">Érvénytelen szülői kód</translation>
 <translation id="332587331255250389">Cserélje ki az akkumulátort</translation>
+<translation id="3335825575923019462">Biztosan kikapcsolja az automatikus kattintásokat?</translation>
 <translation id="3351879221545518001">Folyamatban van a képernyő átküldése.</translation>
 <translation id="3364721542077212959">Érintőceruza eszközök</translation>
 <translation id="3368922792935385530">Kapcsolódva</translation>
@@ -400,6 +401,7 @@
 <translation id="7348093485538360975">Képernyő-billentyűzet</translation>
 <translation id="735745346212279324">A VPN nincs csatlakoztatva</translation>
 <translation id="7377169924702866686">A Caps Lock be van kapcsolva.</translation>
+<translation id="7378594059915113390">Médiatartalmak vezérlői</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> s</translation>
 <translation id="7398254312354928459">A rendszer hálózati kapcsolatot váltott</translation>
 <translation id="7405710164030118432">Az eszköz zárolásának feloldásához adja meg a Family Link szülői hozzáférési kódot</translation>
@@ -485,6 +487,7 @@
 <translation id="8649101189709089199">Felolvasás</translation>
 <translation id="8652175077544655965">Beállítások bezárása</translation>
 <translation id="8653151467777939995">Az értesítési beállítások megjelenítése. Az értesítések be vannak kapcsolva</translation>
+<translation id="8664483332071009680">Letiltja az automatikus kattintásokat?</translation>
 <translation id="8664753092453405566">A hálózatok listájának megjelenítése. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Kis teljesítményű töltő csatlakoztatva</translation>
diff --git a/ash/strings/ash_strings_id.xtb b/ash/strings/ash_strings_id.xtb
index 61f43d8..9955e75 100644
--- a/ash/strings/ash_strings_id.xtb
+++ b/ash/strings/ash_strings_id.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> tetap dapat login di lain waktu.</translation>
 <translation id="3321628682574733415">Kode induk salah</translation>
 <translation id="332587331255250389">Harap ganti baterai</translation>
+<translation id="3335825575923019462">Yakin ingin menonaktifkan klik otomatis?</translation>
 <translation id="3351879221545518001">Anda sedang mentransmisi layar.</translation>
 <translation id="3364721542077212959">Alat stilus</translation>
 <translation id="3368922792935385530">Tersambung</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">Keyboard di layar</translation>
 <translation id="735745346212279324">VPN terputus</translation>
 <translation id="7377169924702866686">Caps Lock aktif.</translation>
+<translation id="7378594059915113390">Kontrol Media</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" />d</translation>
 <translation id="7398254312354928459">Koneksi jaringan dialihkan</translation>
 <translation id="7405710164030118432">Untuk membuka kunci perangkat, masukkan kode akses orang tua Family Link</translation>
@@ -484,6 +486,7 @@
 <translation id="8649101189709089199">Klik untuk Diucapkan</translation>
 <translation id="8652175077544655965">Tutup setelan</translation>
 <translation id="8653151467777939995">Tampilkan setelan notifikasi. Notifikasi aktif.</translation>
+<translation id="8664483332071009680">Nonaktifkan fitur klik otomatis?</translation>
 <translation id="8664753092453405566">Tampilkan daftar jaringan. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Pengisi daya rendah terpasang</translation>
diff --git a/ash/strings/ash_strings_it.xtb b/ash/strings/ash_strings_it.xtb
index f210bd0..6f5851b 100644
--- a/ash/strings/ash_strings_it.xtb
+++ b/ash/strings/ash_strings_it.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> può ancora accedere in seguito.</translation>
 <translation id="3321628682574733415">Codice genitore errato</translation>
 <translation id="332587331255250389">Sostituisci la batteria</translation>
+<translation id="3335825575923019462">Vuoi disattivare i clic automatici?</translation>
 <translation id="3351879221545518001">Al momento stai trasmettendo lo schermo.</translation>
 <translation id="3364721542077212959">Strumenti per stilo</translation>
 <translation id="3368922792935385530">Connessa</translation>
@@ -400,6 +401,7 @@
 <translation id="7348093485538360975">Tastiera sullo schermo</translation>
 <translation id="735745346212279324">VPN scollegata</translation>
 <translation id="7377169924702866686">La funzione Bloc maiusc è attiva.</translation>
+<translation id="7378594059915113390">Controlli multimediali</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> s</translation>
 <translation id="7398254312354928459">Connessione di rete automatica</translation>
 <translation id="7405710164030118432">Per sbloccare il dispositivo, inserisci il codice di accesso genitore di Family Link</translation>
@@ -485,6 +487,7 @@
 <translation id="8649101189709089199">Seleziona per ascoltare</translation>
 <translation id="8652175077544655965">Chiudi impostazioni</translation>
 <translation id="8653151467777939995">Mostra impostazioni di notifica. Le notifiche sono attive</translation>
+<translation id="8664483332071009680">Disattivare i clic automatici?</translation>
 <translation id="8664753092453405566">Mostra elenco di reti. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Caricabatterie a basso consumo collegato</translation>
diff --git a/ash/strings/ash_strings_iw.xtb b/ash/strings/ash_strings_iw.xtb
index 420c587..82305eb 100644
--- a/ash/strings/ash_strings_iw.xtb
+++ b/ash/strings/ash_strings_iw.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101">ל-<ph name="USER_EMAIL_ADDRESS" /> תהיה עדיין אפשרות להיכנס לחשבון מאוחר יותר.</translation>
 <translation id="3321628682574733415">קוד הורה שגוי</translation>
 <translation id="332587331255250389">יש להחליף את הסוללה</translation>
+<translation id="3335825575923019462">הקליקים האוטומטיים יושבתו. האם להמשיך?</translation>
 <translation id="3351879221545518001">מתבצעת עכשיו העברה של מסך.</translation>
 <translation id="3364721542077212959">כלי סטיילוס</translation>
 <translation id="3368922792935385530">מחובר</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">מקלדת על המסך</translation>
 <translation id="735745346212279324">‏VPN מנותק</translation>
 <translation id="7377169924702866686">‏Caps Lock מופעל.</translation>
+<translation id="7378594059915113390">פקדי מדיה</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> שניות</translation>
 <translation id="7398254312354928459">חיבור הרשת הוחלף</translation>
 <translation id="7405710164030118432">‏כדי לבטל את נעילת המכשיר, יש להזין את קוד גישת ההורה של Family Link</translation>
@@ -486,6 +488,7 @@
 <translation id="8649101189709089199">הקראה</translation>
 <translation id="8652175077544655965">סגירת ההגדרות</translation>
 <translation id="8653151467777939995">צפייה בהגדרות של הודעות. ההודעות מופעלות</translation>
+<translation id="8664483332071009680">האם להשבית את הקליקים האוטומטיים?</translation>
 <translation id="8664753092453405566">צפייה ברשימת הרשתות. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">חובר מטען בעל מתח נמוך</translation>
diff --git a/ash/strings/ash_strings_ja.xtb b/ash/strings/ash_strings_ja.xtb
index 5374325..a27d8c6 100644
--- a/ash/strings/ash_strings_ja.xtb
+++ b/ash/strings/ash_strings_ja.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> が後でログインすることは引き続き可能です。</translation>
 <translation id="3321628682574733415">保護者のコードが間違っています</translation>
 <translation id="332587331255250389">バッテリーを交換してください</translation>
+<translation id="3335825575923019462">自動クリックを無効にしてもよろしいですか?</translation>
 <translation id="3351879221545518001">現在、画面をキャストしています。</translation>
 <translation id="3364721542077212959">タッチペン ツール</translation>
 <translation id="3368922792935385530">接続済み</translation>
@@ -400,6 +401,7 @@
 <translation id="735745346212279324">VPN が切断されました</translation>
 <translation id="7377169924702866686">CapsLock 
 がオンになっています。</translation>
+<translation id="7378594059915113390">メディアの操作</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> 秒</translation>
 <translation id="7398254312354928459">ネットワーク接続が切り替えられました</translation>
 <translation id="7405710164030118432">このデバイスのロックを解除するには、ファミリー リンクの保護者のアクセスコードを入力してください</translation>
@@ -485,6 +487,7 @@
 <translation id="8649101189709089199">選択して読み上げ</translation>
 <translation id="8652175077544655965">設定を閉じます</translation>
 <translation id="8653151467777939995">通知設定を表示します。通知はオンです</translation>
+<translation id="8664483332071009680">自動クリックを無効にしますか?</translation>
 <translation id="8664753092453405566">ネットワーク リストを表示します。<ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">低電力の充電器に接続されています</translation>
diff --git a/ash/strings/ash_strings_lt.xtb b/ash/strings/ash_strings_lt.xtb
index e31e214..0572dfe 100644
--- a/ash/strings/ash_strings_lt.xtb
+++ b/ash/strings/ash_strings_lt.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> vis tiek galės vėliau prisijungti.</translation>
 <translation id="3321628682574733415">Netinkamas tėvų kodas</translation>
 <translation id="332587331255250389">Pakeiskite akumuliatorių</translation>
+<translation id="3335825575923019462">Ar tikrai norite išjungti automatinius paspaudimus?</translation>
 <translation id="3351879221545518001">Šiuo metu perduodate ekrano vaizdą.</translation>
 <translation id="3364721542077212959">Rašiklio įrankiai</translation>
 <translation id="3368922792935385530">Prijungta</translation>
@@ -400,6 +401,7 @@
 <translation id="7348093485538360975">Ekraninė klaviatūra</translation>
 <translation id="735745346212279324">VPN atjungtas</translation>
 <translation id="7377169924702866686">Įjungtas didžiųjų raidžių klavišas.</translation>
+<translation id="7378594059915113390">Medijos valdikliai</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> sek.</translation>
 <translation id="7398254312354928459">Perjungtas tinklo ryšys</translation>
 <translation id="7405710164030118432">Jei norite atrakinti įrenginį, įveskite „Family Link“ tėvų prieigos kodą</translation>
@@ -485,6 +487,7 @@
 <translation id="8649101189709089199">Teksto ištarimas</translation>
 <translation id="8652175077544655965">Uždaryti nustatymus</translation>
 <translation id="8653151467777939995">Rodyti pranešimų nustatymus. Pranešimai įjungti</translation>
+<translation id="8664483332071009680">Išjungti automatinius paspaudimus?</translation>
 <translation id="8664753092453405566">Rodyti tinklų sąrašą. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Prijungtas mažos galios įkroviklis</translation>
diff --git a/ash/strings/ash_strings_ms.xtb b/ash/strings/ash_strings_ms.xtb
index a80c7e9..37168c5 100644
--- a/ash/strings/ash_strings_ms.xtb
+++ b/ash/strings/ash_strings_ms.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> masih dapat log masuk nanti.</translation>
 <translation id="3321628682574733415">Kod ibu bapa salah</translation>
 <translation id="332587331255250389">Sila gantikan bateri</translation>
+<translation id="3335825575923019462">Adakah anda pasti mahu melumpuhkan klik automatik?</translation>
 <translation id="3351879221545518001">Anda sedang menghantar skrin.</translation>
 <translation id="3364721542077212959">Alatan stilus</translation>
 <translation id="3368922792935385530">Disambungkan</translation>
@@ -400,6 +401,7 @@
 <translation id="7348093485538360975">Papan kekunci pada skrin</translation>
 <translation id="735745346212279324">VPN diputuskan sambungan</translation>
 <translation id="7377169924702866686">Caps Lock dihidupkan.</translation>
+<translation id="7378594059915113390">Kawalan Media</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" />s</translation>
 <translation id="7398254312354928459">Beralih sambungan rangkaian</translation>
 <translation id="7405710164030118432">Untuk membuka kunci peranti, masukkan kod akses ibu bapa Family Link anda</translation>
@@ -485,6 +487,7 @@
 <translation id="8649101189709089199">Pilih untuk Bercakap</translation>
 <translation id="8652175077544655965">Tutup tetapan</translation>
 <translation id="8653151467777939995">Tunjukkan tetapan pemberitahuan. Pemberitahuan dihidupkan</translation>
+<translation id="8664483332071009680">Lumpuhkan klik automatik?</translation>
 <translation id="8664753092453405566">Tunjukkan senarai rangkaian. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Pengecas berkuasa rendah disambungkan</translation>
diff --git a/ash/strings/ash_strings_nl.xtb b/ash/strings/ash_strings_nl.xtb
index 81430a7..975e904 100644
--- a/ash/strings/ash_strings_nl.xtb
+++ b/ash/strings/ash_strings_nl.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> kan later nog inloggen.</translation>
 <translation id="3321628682574733415">Onjuiste oudercode</translation>
 <translation id="332587331255250389">Vervang de batterij</translation>
+<translation id="3335825575923019462">Weet je zeker dat je 'Automatische klikken' wilt uitschakelen?</translation>
 <translation id="3351879221545518001">Je cast momenteel een scherm.</translation>
 <translation id="3364721542077212959">Stylustools</translation>
 <translation id="3368922792935385530">Verbonden</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">Schermtoetsenbord</translation>
 <translation id="735745346212279324">Verbinding met VPN verbroken</translation>
 <translation id="7377169924702866686">Caps Lock is ingeschakeld.</translation>
+<translation id="7378594059915113390">Mediabediening</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" />s</translation>
 <translation id="7398254312354928459">Overgeschakeld naar andere netwerkverbinding</translation>
 <translation id="7405710164030118432">Geef je Family Link-toegangscode voor ouders op om het apparaat te ontgrendelen</translation>
@@ -484,6 +486,7 @@
 <translation id="8649101189709089199">Selecteer om uitgesproken te worden</translation>
 <translation id="8652175077544655965">Instellingen sluiten</translation>
 <translation id="8653151467777939995">Instellingen voor meldingen weergeven. Meldingen zijn ingeschakeld</translation>
+<translation id="8664483332071009680">'Automatische klikken' uitschakelen?</translation>
 <translation id="8664753092453405566">Netwerklijst weergeven. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Laag-vermogen-lader aangesloten</translation>
diff --git a/ash/strings/ash_strings_no.xtb b/ash/strings/ash_strings_no.xtb
index 289847d..11f2fa3 100644
--- a/ash/strings/ash_strings_no.xtb
+++ b/ash/strings/ash_strings_no.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> kan fortsatt logge på senere.</translation>
 <translation id="3321628682574733415">Feil kode for foreldertilgang</translation>
 <translation id="332587331255250389">Bytt ut batteriet</translation>
+<translation id="3335825575923019462">Er du sikker på at du vil slå av automatiske klikk?</translation>
 <translation id="3351879221545518001">Du caster for øyeblikket skjermen.</translation>
 <translation id="3364721542077212959">Pekepennverktøy</translation>
 <translation id="3368922792935385530">Tilkoblet</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">Skjermtastatur</translation>
 <translation id="735745346212279324">VPN frakoblet</translation>
 <translation id="7377169924702866686">Caps Lock er på.</translation>
+<translation id="7378594059915113390">Mediekontroller</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> s</translation>
 <translation id="7398254312354928459">Byttet nettverkstilkobling</translation>
 <translation id="7405710164030118432">For å låse opp enheten, skriv inn koden din for foreldretilgang til Family Link</translation>
@@ -484,6 +486,7 @@
 <translation id="8649101189709089199">Tekstopplesing</translation>
 <translation id="8652175077544655965">Lukk innstillingene</translation>
 <translation id="8653151467777939995">Vis innstillinger for varsler. Varsler er på</translation>
+<translation id="8664483332071009680">Vil du slå av automatiske klikk?</translation>
 <translation id="8664753092453405566">Vis nettverksliste. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Laveffektslader er tilkoblet</translation>
diff --git a/ash/strings/ash_strings_pt-BR.xtb b/ash/strings/ash_strings_pt-BR.xtb
index 934e8cd..da7d4a4 100644
--- a/ash/strings/ash_strings_pt-BR.xtb
+++ b/ash/strings/ash_strings_pt-BR.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101">O usuário <ph name="USER_EMAIL_ADDRESS" /> ainda poderá fazer login mais tarde.</translation>
 <translation id="3321628682574733415">Código de pai/mãe incorreto</translation>
 <translation id="332587331255250389">Substitua a bateria</translation>
+<translation id="3335825575923019462">Tem certeza de que quer desativar os cliques automáticos?</translation>
 <translation id="3351879221545518001">Você está transmitindo a tela.</translation>
 <translation id="3364721542077212959">Ferramentas da stylus</translation>
 <translation id="3368922792935385530">Conectado</translation>
@@ -401,6 +402,7 @@
 <translation id="7348093485538360975">Teclado virtual</translation>
 <translation id="735745346212279324">VPN desconectada</translation>
 <translation id="7377169924702866686">O Caps Lock está ativado.</translation>
+<translation id="7378594059915113390">Controles de mídia</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" />s</translation>
 <translation id="7398254312354928459">Conexão de rede alterada</translation>
 <translation id="7405710164030118432">Para desbloquear o dispositivo, insira seu código de acesso de pai/mãe do Family Link</translation>
@@ -486,6 +488,7 @@
 <translation id="8649101189709089199">Selecionar para ouvir</translation>
 <translation id="8652175077544655965">Fechar configurações</translation>
 <translation id="8653151467777939995">Mostrar configurações de notificação. As notificações estão ativadas</translation>
+<translation id="8664483332071009680">Desativar cliques automáticos?</translation>
 <translation id="8664753092453405566">Mostrar lista de redes. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Carregador de baixa potência conectado</translation>
diff --git a/ash/strings/ash_strings_pt-PT.xtb b/ash/strings/ash_strings_pt-PT.xtb
index 4712081..b017d60 100644
--- a/ash/strings/ash_strings_pt-PT.xtb
+++ b/ash/strings/ash_strings_pt-PT.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> pode iniciar sessão mais tarde.</translation>
 <translation id="3321628682574733415">Código parental incorreto</translation>
 <translation id="332587331255250389">Substitua a bateria</translation>
+<translation id="3335825575923019462">Tem a certeza de que pretende desativar os cliques automáticos?</translation>
 <translation id="3351879221545518001">Está a transmitir atualmente o ecrã.</translation>
 <translation id="3364721542077212959">Ferramentas da caneta stylus</translation>
 <translation id="3368922792935385530">Ligado</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">Teclado no ecrã</translation>
 <translation id="735745346212279324">VPN desligado</translation>
 <translation id="7377169924702866686">Caps Lock ativado.</translation>
+<translation id="7378594059915113390">Controlos de multimédia</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> seg</translation>
 <translation id="7398254312354928459">Ligação de rede alterada</translation>
 <translation id="7405710164030118432">Para desbloquear este dispositivo, introduza o código de acesso parental do seu Family Link.</translation>
@@ -484,6 +486,7 @@
 <translation id="8649101189709089199">Selecionar para falar</translation>
 <translation id="8652175077544655965">Fechar definições</translation>
 <translation id="8653151467777939995">Mostrar definições de notificação. As notificações estão ativadas</translation>
+<translation id="8664483332071009680">Pretende desativar os cliques automáticos?</translation>
 <translation id="8664753092453405566">Mostrar lista de redes. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Carregador de baixo consumo ligado</translation>
diff --git a/ash/strings/ash_strings_ro.xtb b/ash/strings/ash_strings_ro.xtb
index 48f8c96..7161a59 100644
--- a/ash/strings/ash_strings_ro.xtb
+++ b/ash/strings/ash_strings_ro.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> se poate conecta în continuare mai târziu.</translation>
 <translation id="3321628682574733415">Codul pentru părinte este incorect</translation>
 <translation id="332587331255250389">Înlocuiește bateria</translation>
+<translation id="3335825575923019462">Sigur vrei să dezactivezi clicurile automate?</translation>
 <translation id="3351879221545518001">În prezent proiectezi ecranul.</translation>
 <translation id="3364721542077212959">Instrumente pentru creion</translation>
 <translation id="3368922792935385530">Conectată</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">Tastatură pe ecran</translation>
 <translation id="735745346212279324">Rețea VPN deconectată</translation>
 <translation id="7377169924702866686">Tasta Caps Lock este activată.</translation>
+<translation id="7378594059915113390">Comenzi media</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" />s</translation>
 <translation id="7398254312354928459">Conexiunea la rețea a fost comutată</translation>
 <translation id="7405710164030118432">Pentru a debloca dispozitivul, introdu codul de acces parental pentru Family Link</translation>
@@ -484,6 +486,7 @@
 <translation id="8649101189709089199">Selectează și ascultă</translation>
 <translation id="8652175077544655965">Închide setările</translation>
 <translation id="8653151467777939995">Afișează setările pentru notificări. Notificările sunt activate</translation>
+<translation id="8664483332071009680">Dezactivezi clicurile automate?</translation>
 <translation id="8664753092453405566">Afișează lista de rețele. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">A fost conectat un încărcător de putere joasă</translation>
diff --git a/ash/strings/ash_strings_sk.xtb b/ash/strings/ash_strings_sk.xtb
index a08f64f..a00c408 100644
--- a/ash/strings/ash_strings_sk.xtb
+++ b/ash/strings/ash_strings_sk.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> sa bude môcť stále prihlásiť neskôr.</translation>
 <translation id="3321628682574733415">Nesprávny kód rodiča</translation>
 <translation id="332587331255250389">Vymeňte batériu</translation>
+<translation id="3335825575923019462">Naozaj chcete deaktivovať automatické kliknutia?</translation>
 <translation id="3351879221545518001">Momentálne prenášate obrazovku.</translation>
 <translation id="3364721542077212959">Nástroje pre dotykové pero</translation>
 <translation id="3368922792935385530">Pripojené</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">Klávesnica na obrazovke</translation>
 <translation id="735745346212279324">Sieť VPN je odpojená</translation>
 <translation id="7377169924702866686">Kláves Caps Lock je zapnutý.</translation>
+<translation id="7378594059915113390">Ovládacie prvky médií</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> s</translation>
 <translation id="7398254312354928459">Prepnuté sieťové pripojenie</translation>
 <translation id="7405710164030118432">Ak chcete toto zariadenie odomknúť, zadajte prístupový kód rodiča Family Link</translation>
@@ -484,6 +486,7 @@
 <translation id="8649101189709089199">Počúvanie vybraného textu</translation>
 <translation id="8652175077544655965">Zatvoriť nastavenia</translation>
 <translation id="8653151467777939995">Zobraziť nastavenia upozornení. Upozornenia sú zapnuté</translation>
+<translation id="8664483332071009680">Chcete deaktivovať automatické kliknutia?</translation>
 <translation id="8664753092453405566">Zobraziť zoznam sietí. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270 °</translation>
 <translation id="8676770494376880701">Pripojila sa nabíjačka s nízkym výkonom</translation>
diff --git a/ash/strings/ash_strings_sr.xtb b/ash/strings/ash_strings_sr.xtb
index 92e40d0f..c6a6886 100644
--- a/ash/strings/ash_strings_sr.xtb
+++ b/ash/strings/ash_strings_sr.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> ће и даље моћи да се пријави касније.</translation>
 <translation id="3321628682574733415">Нетачан кôд родитеља</translation>
 <translation id="332587331255250389">Замените батерију</translation>
+<translation id="3335825575923019462">Желите ли стварно да онемогућите аутоматске кликове?</translation>
 <translation id="3351879221545518001">Тренутно пребацујете екран.</translation>
 <translation id="3364721542077212959">Алатке за писаљку</translation>
 <translation id="3368922792935385530">Повезан</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">Тастатура на екрану</translation>
 <translation id="735745346212279324">Веза са VPN-ом је прекинута</translation>
 <translation id="7377169924702866686">Caps Lock је укључен.</translation>
+<translation id="7378594059915113390">Контроле за медије</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> сек</translation>
 <translation id="7398254312354928459">Мрежна веза је промењена</translation>
 <translation id="7405710164030118432">Да бисте откључали уређај, унесите приступни кôд родитеља за Family Link</translation>
@@ -484,6 +486,7 @@
 <translation id="8649101189709089199">Изаберите за говор</translation>
 <translation id="8652175077544655965">Затворите подешавања</translation>
 <translation id="8653151467777939995">Прегледајте подешавања обавештења. Обавештења су укључена</translation>
+<translation id="8664483332071009680">Желите ли да онемогућите аутоматске кликове?</translation>
 <translation id="8664753092453405566">Прегледајте листу мрежа. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Повезан је пуњач мале снаге</translation>
diff --git a/ash/strings/ash_strings_ta.xtb b/ash/strings/ash_strings_ta.xtb
index c4b7ee6..df04274 100644
--- a/ash/strings/ash_strings_ta.xtb
+++ b/ash/strings/ash_strings_ta.xtb
@@ -112,6 +112,7 @@
 <translation id="2597972630681408282">நைட் லைட்: <ph name="NIGHT_LIGHT_STATUS" /></translation>
 <translation id="2617342710774726426">சிம் கார்டு பூட்டப்பட்டுள்ளது</translation>
 <translation id="2653659639078652383">சமர்ப்பி</translation>
+<translation id="2658778018866295321">கிளிக் செய்து இழுக்கும்</translation>
 <translation id="2700493154570097719">எனது விசைப்பலகையை அமை</translation>
 <translation id="2718395828230677721">நைட் லைட்</translation>
 <translation id="2727977024730340865">குறைந்த சக்தியிலான சார்ஜர் செருகப்பட்டுள்ளது. பேட்டரி சார்ஜிங் நம்பகமானதாக இல்லாமல் இருக்கலாம்.</translation>
diff --git a/ash/strings/ash_strings_th.xtb b/ash/strings/ash_strings_th.xtb
index f613f65..b2cc9be5 100644
--- a/ash/strings/ash_strings_th.xtb
+++ b/ash/strings/ash_strings_th.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> ยังมีสิทธิ์ลงชื่อเข้าใช้ภายหลังได้</translation>
 <translation id="3321628682574733415">รหัสผู้ปกครองไม่ถูกต้อง</translation>
 <translation id="332587331255250389">โปรดเปลี่ยนแบตเตอรี่</translation>
+<translation id="3335825575923019462">แน่ใจไหมว่าต้องการปิดใช้การคลิกอัตโนมัติ</translation>
 <translation id="3351879221545518001">คุณกำลังแคสต์หน้าจออยู่</translation>
 <translation id="3364721542077212959">เครื่องมือสไตลัส</translation>
 <translation id="3368922792935385530">เชื่อมต่อแล้ว</translation>
@@ -400,6 +401,7 @@
 <translation id="7348093485538360975">แป้นพิมพ์บนหน้าจอ</translation>
 <translation id="735745346212279324">ยกเลิกการเชื่อมต่อ VPN แล้ว</translation>
 <translation id="7377169924702866686">Caps Lock เปิดอยู่</translation>
+<translation id="7378594059915113390">การควบคุมสื่อ</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> วินาที</translation>
 <translation id="7398254312354928459">เปลี่ยนการเชื่อมต่อเครือข่ายแล้ว</translation>
 <translation id="7405710164030118432">ป้อนรหัสการเข้าถึง Family Link ของผู้ปกครองเพื่อปลดล็อกอุปกรณ์</translation>
@@ -485,6 +487,7 @@
 <translation id="8649101189709089199">เลือกเพื่อให้อ่าน</translation>
 <translation id="8652175077544655965">ปิดการตั้งค่า</translation>
 <translation id="8653151467777939995">แสดงการตั้งค่าการแจ้งเตือน การแจ้งเตือนเปิดอยู่</translation>
+<translation id="8664483332071009680">ปิดใช้การคลิกอัตโนมัติไหม</translation>
 <translation id="8664753092453405566">แสดงรายการเครือข่าย <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">เชื่อมต่อกับที่ชาร์จพลังงานต่ำ</translation>
diff --git a/ash/strings/ash_strings_uk.xtb b/ash/strings/ash_strings_uk.xtb
index cc2c44f..9864047 100644
--- a/ash/strings/ash_strings_uk.xtb
+++ b/ash/strings/ash_strings_uk.xtb
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> усе одно зможе ввійти в обліковий запис.</translation>
 <translation id="3321628682574733415">Неправильний код доступу батьків</translation>
 <translation id="332587331255250389">Замініть акумулятор</translation>
+<translation id="3335825575923019462">Вимкнути автоматичні кліки?</translation>
 <translation id="3351879221545518001">Зараз ви транслюєте екран.</translation>
 <translation id="3364721542077212959">Інструменти стилуса</translation>
 <translation id="3368922792935385530">Підключено</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">Екранна клавіатура</translation>
 <translation id="735745346212279324">VPN від’єднано</translation>
 <translation id="7377169924702866686">Клавішу Caps Lock увімкнено.</translation>
+<translation id="7378594059915113390">Елементи керування медіа</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /></translation>
 <translation id="7398254312354928459">З’єднання з мережею змінено</translation>
 <translation id="7405710164030118432">Щоб розблокувати пристрій, введіть код доступу батьків із Family Link</translation>
@@ -484,6 +486,7 @@
 <translation id="8649101189709089199">Читання з екрана</translation>
 <translation id="8652175077544655965">Закрити налаштування</translation>
 <translation id="8653151467777939995">Показати налаштування сповіщень. Сповіщення ввімкнено</translation>
+<translation id="8664483332071009680">Вимкнути автоматичні кліки?</translation>
 <translation id="8664753092453405566">Показати список мереж. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Зарядний пристрій низької потужності підключено</translation>
diff --git a/ash/strings/ash_strings_zh-TW.xtb b/ash/strings/ash_strings_zh-TW.xtb
index 8ba0a90..bd5ce72 100644
--- a/ash/strings/ash_strings_zh-TW.xtb
+++ b/ash/strings/ash_strings_zh-TW.xtb
@@ -8,7 +8,7 @@
 <translation id="1056775291175587022">沒有網路</translation>
 <translation id="1059194134494239015"><ph name="DISPLAY_NAME" />:<ph name="RESOLUTION" /></translation>
 <translation id="109942774857561566">我好無聊</translation>
-<translation id="1104084341931202936">顯示協助工具設定</translation>
+<translation id="1104084341931202936">顯示無障礙設定</translation>
 <translation id="1104621072296271835">完成連結可進一步提升裝置效能</translation>
 <translation id="112308213915226829">自動隱藏檔案櫃</translation>
 <translation id="1153356358378277386">配對裝置</translation>
@@ -158,6 +158,7 @@
 <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> 日後仍可登入。</translation>
 <translation id="3321628682574733415">家長存取碼不正確</translation>
 <translation id="332587331255250389">請更換電池</translation>
+<translation id="3335825575923019462">確定要停用自動點擊功能嗎?</translation>
 <translation id="3351879221545518001">你目前正在投放畫面。</translation>
 <translation id="3364721542077212959">觸控筆工具</translation>
 <translation id="3368922792935385530">已連線</translation>
@@ -269,7 +270,7 @@
 <translation id="5168181903108465623">可用的投放裝置</translation>
 <translation id="5207949376430453814">醒目顯示文字插入點</translation>
 <translation id="5222676887888702881">登出</translation>
-<translation id="523505283826916779">協助工具設定</translation>
+<translation id="523505283826916779">無障礙設定</translation>
 <translation id="5260676007519551770">桌面 4</translation>
 <translation id="5283198616748585639">增加 1 分鐘</translation>
 <translation id="5302048478445481009">語言</translation>
@@ -374,7 +375,7 @@
 <translation id="6910714959251846841">必須對你的裝置執行 Powerwash 才能安裝這項更新。進一步瞭解最新的 <ph name="SYSTEM_APP_NAME" />更新內容。</translation>
 <translation id="6911468394164995108">加入其他網路...</translation>
 <translation id="6972754398087986839">開始使用</translation>
-<translation id="6981982820502123353">協助工具</translation>
+<translation id="6981982820502123353">無障礙設定</translation>
 <translation id="698231206551913481">將這位使用者移除後,與這位使用者相關聯的所有檔案和本機資料都會遭到永久刪除。</translation>
 <translation id="7015766095477679451"><ph name="COME_BACK_TIME" /> 裝置就會解除鎖定。</translation>
 <translation id="7025533177575372252">將 <ph name="DEVICE_NAME" /> 連結到你的手機</translation>
@@ -399,6 +400,7 @@
 <translation id="7348093485538360975">螢幕小鍵盤</translation>
 <translation id="735745346212279324">已中斷 VPN 連線</translation>
 <translation id="7377169924702866686">大寫鍵已啟用。</translation>
+<translation id="7378594059915113390">媒體控制項</translation>
 <translation id="7384299914270925461"><ph name="SIGN" /><ph name="MINUTES_REMAINING" />:<ph name="SECONDS_REMAINING" /> 秒</translation>
 <translation id="7398254312354928459">已切換網路連線</translation>
 <translation id="7405710164030118432">如要將這部裝置解鎖,請輸入你的 Family Link 家長存取碼</translation>
@@ -484,6 +486,7 @@
 <translation id="8649101189709089199">隨選朗讀</translation>
 <translation id="8652175077544655965">關閉設定</translation>
 <translation id="8653151467777939995">顯示通知設定。已開啟所有通知</translation>
+<translation id="8664483332071009680">要停用自動點擊功能嗎?</translation>
 <translation id="8664753092453405566">顯示網路清單。<ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270 度</translation>
 <translation id="8676770494376880701">已連接低功率充電器</translation>
diff --git a/ash/system/accessibility/tray_accessibility.cc b/ash/system/accessibility/tray_accessibility.cc
index ba303d3f..1ba417f 100644
--- a/ash/system/accessibility/tray_accessibility.cc
+++ b/ash/system/accessibility/tray_accessibility.cc
@@ -12,6 +12,7 @@
 #include "ash/magnifier/docked_magnifier_controller_impl.h"
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/ash_view_ids.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -389,18 +390,15 @@
 
 void AccessibilityDetailedView::ShowSettings() {
   if (TrayPopupUtils::CanOpenWebUISettings()) {
-    Shell::Get()
-        ->system_tray_model()
-        ->client_ptr()
-        ->ShowAccessibilitySettings();
-    CloseBubble();
+    CloseBubble();  // Deletes |this|.
+    Shell::Get()->system_tray_model()->client()->ShowAccessibilitySettings();
   }
 }
 
 void AccessibilityDetailedView::ShowHelp() {
   if (TrayPopupUtils::CanOpenWebUISettings()) {
-    Shell::Get()->system_tray_model()->client_ptr()->ShowAccessibilityHelp();
-    CloseBubble();
+    CloseBubble();  // Deletes |this|.
+    Shell::Get()->system_tray_model()->client()->ShowAccessibilityHelp();
   }
 }
 
diff --git a/ash/system/bluetooth/bluetooth_detailed_view.cc b/ash/system/bluetooth/bluetooth_detailed_view.cc
index eb2a182..1f7a66a 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view.cc
+++ b/ash/system/bluetooth/bluetooth_detailed_view.cc
@@ -8,6 +8,7 @@
 #include <memory>
 #include <utility>
 
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -274,8 +275,8 @@
 
 void BluetoothDetailedView::ShowSettings() {
   if (TrayPopupUtils::CanOpenWebUISettings()) {
-    Shell::Get()->system_tray_model()->client_ptr()->ShowBluetoothSettings();
-    CloseBubble();
+    CloseBubble();  // Deletes |this|.
+    Shell::Get()->system_tray_model()->client()->ShowBluetoothSettings();
   }
 }
 
diff --git a/ash/system/bluetooth/tray_bluetooth_helper_legacy.cc b/ash/system/bluetooth/tray_bluetooth_helper_legacy.cc
index 72f60a2..21f10f0 100644
--- a/ash/system/bluetooth/tray_bluetooth_helper_legacy.cc
+++ b/ash/system/bluetooth/tray_bluetooth_helper_legacy.cc
@@ -7,6 +7,7 @@
 #include <string>
 #include <utility>
 
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/shell.h"
 #include "ash/system/bluetooth/bluetooth_power_controller.h"
 #include "ash/system/model/system_tray_model.h"
@@ -199,7 +200,7 @@
     return;
   }
   // Show pairing dialog for the unpaired device.
-  Shell::Get()->system_tray_model()->client_ptr()->ShowBluetoothPairingDialog(
+  Shell::Get()->system_tray_model()->client()->ShowBluetoothPairingDialog(
       device->GetAddress(), device->GetNameForDisplay(), device->IsPaired(),
       device->IsConnected());
 }
diff --git a/ash/system/ime/tray_ime_chromeos.cc b/ash/system/ime/tray_ime_chromeos.cc
index f7162a9..65d70f5 100644
--- a/ash/system/ime/tray_ime_chromeos.cc
+++ b/ash/system/ime/tray_ime_chromeos.cc
@@ -9,6 +9,7 @@
 
 #include "ash/ime/ime_controller.h"
 #include "ash/keyboard/ui/keyboard_util.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -78,8 +79,8 @@
 
 void IMEDetailedView::ShowSettings() {
   base::RecordAction(base::UserMetricsAction("StatusArea_IME_Detailed"));
-  Shell::Get()->system_tray_model()->client_ptr()->ShowIMESettings();
-  CloseBubble();
+  CloseBubble();  // Deletes |this|.
+  Shell::Get()->system_tray_model()->client()->ShowIMESettings();
 }
 
 const char* IMEDetailedView::GetClassName() const {
diff --git a/ash/system/ime_menu/ime_menu_tray.cc b/ash/system/ime_menu/ime_menu_tray.cc
index 9e4543c..e399554 100644
--- a/ash/system/ime_menu/ime_menu_tray.cc
+++ b/ash/system/ime_menu/ime_menu_tray.cc
@@ -10,6 +10,7 @@
 #include "ash/keyboard/ui/keyboard_controller.h"
 #include "ash/keyboard/virtual_keyboard_controller.h"
 #include "ash/public/cpp/ash_constants.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/root_window_controller.h"
 #include "ash/session/session_controller_impl.h"
@@ -64,7 +65,7 @@
 // Shows language and input settings page.
 void ShowIMESettings() {
   base::RecordAction(base::UserMetricsAction("StatusArea_IME_Detailed"));
-  Shell::Get()->system_tray_model()->client_ptr()->ShowIMESettings();
+  Shell::Get()->system_tray_model()->client()->ShowIMESettings();
 }
 
 // Returns true if the current screen is login or lock screen.
diff --git a/ash/system/locale/locale_detailed_view.cc b/ash/system/locale/locale_detailed_view.cc
index dbc14de..27f6a3b 100644
--- a/ash/system/locale/locale_detailed_view.cc
+++ b/ash/system/locale/locale_detailed_view.cc
@@ -4,6 +4,7 @@
 
 #include "ash/system/locale/locale_detailed_view.h"
 
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -145,7 +146,7 @@
   const std::string locale_iso_code = it->second;
   if (locale_iso_code !=
       Shell::Get()->system_tray_model()->locale()->current_locale_iso_code()) {
-    Shell::Get()->system_tray_model()->client_ptr()->SetLocaleAndExit(
+    Shell::Get()->system_tray_model()->client()->SetLocaleAndExit(
         locale_iso_code);
   }
 }
@@ -155,4 +156,4 @@
 }
 
 }  // namespace tray
-}  // namespace ash
\ No newline at end of file
+}  // namespace ash
diff --git a/ash/system/message_center/unified_message_center_view_unittest.cc b/ash/system/message_center/unified_message_center_view_unittest.cc
index eeedb18..1eaaff3 100644
--- a/ash/system/message_center/unified_message_center_view_unittest.cc
+++ b/ash/system/message_center/unified_message_center_view_unittest.cc
@@ -15,6 +15,7 @@
 #include "ash/system/unified/unified_system_tray_model.h"
 #include "ash/test/ash_test_base.h"
 #include "base/macros.h"
+#include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
@@ -80,6 +81,7 @@
   }
 
   void TearDown() override {
+    base::RunLoop().RunUntilIdle();
     message_center_view_.reset();
     model_.reset();
     AshTestBase::TearDown();
@@ -126,13 +128,19 @@
 
   void AnimateMessageListToMiddle() { AnimateMessageListToValue(0.5); }
 
-  void AnimateMessageListToEnd() { GetMessageListView()->animation_->End(); }
+  void AnimateMessageListToEnd() {
+    FinishMessageListSlideOutAnimations();
+    GetMessageListView()->animation_->End();
+  }
 
   void AnimateMessageListUntilIdle() {
-    while (GetMessageListView()->animation_->is_animating())
+    while (GetMessageListView()->animation_->is_animating()) {
       GetMessageListView()->animation_->End();
+    }
   }
 
+  void FinishMessageListSlideOutAnimations() { base::RunLoop().RunUntilIdle(); }
+
   gfx::Rect GetMessageViewVisibleBounds(size_t index) {
     gfx::Rect bounds = GetMessageListView()->children()[index]->bounds();
     bounds -= GetScroller()->GetVisibleRect().OffsetFromOrigin();
@@ -236,7 +244,7 @@
   auto* collapse_animation = GetMessageCenterAnimation();
   collapse_animation->SetCurrentValue(0.5);
   message_center_view()->AnimationProgressed(collapse_animation);
-  AnimateMessageListToMiddle();
+  AnimateMessageListToEnd();
   EXPECT_TRUE(message_center_view()->GetVisible());
 
   // The message center is now hidden after all animations complete.
@@ -263,12 +271,7 @@
   // Remove the last notification.
   MessageCenter::Get()->RemoveNotification(id_to_remove, true /* by_user */);
 
-  // The first animation slides the notification out of the list, and the second
-  // animation collapses the list.
-  AnimateMessageListToEnd();
-  AnimateMessageListToValue(0);
-
-  // The scroll position should not change after sliding the notification out
+  // The scroll position should not change before sliding the notification out
   // and instead should wait until the animation finishes.
   EXPECT_EQ(scroll_position, GetScroller()->GetVisibleRect().y());
 
@@ -297,7 +300,7 @@
   const int previous_list_height = GetMessageListView()->height();
 
   MessageCenter::Get()->RemoveNotification(ids.back(), true /* by_user */);
-  AnimateMessageListUntilIdle();
+  AnimateMessageListToEnd();
   EXPECT_TRUE(message_center_view()->GetVisible());
   EXPECT_GT(previous_contents_height, GetScrollerContents()->height());
   EXPECT_GT(previous_list_height, GetMessageListView()->height());
@@ -535,7 +538,7 @@
   EXPECT_TRUE(GetStackingCounter()->GetVisible());
   for (size_t i = 0; i < 5; ++i) {
     MessageCenter::Get()->RemoveNotification(ids[i], true /* by_user */);
-    AnimateMessageListUntilIdle();
+    AnimateMessageListToEnd();
   }
   EXPECT_FALSE(GetStackingCounter()->GetVisible());
 }
@@ -629,7 +632,7 @@
   EXPECT_TRUE(GetStackingCounter()->GetVisible());
   for (size_t i = 0; i < 4; ++i) {
     MessageCenter::Get()->RemoveNotification(ids[i], true /* by_user */);
-    AnimateMessageListUntilIdle();
+    AnimateMessageListToEnd();
   }
   EXPECT_TRUE(GetStackingCounter()->GetVisible());
   EXPECT_FALSE(GetStackingCounterLabel()->GetVisible());
@@ -808,7 +811,7 @@
 
   // Remove the notification and observe that the focus is cleared.
   MessageCenter::Get()->RemoveNotification(id1, true /* by_user */);
-  AnimateMessageListUntilIdle();
+  AnimateMessageListToEnd();
   EXPECT_FALSE(message_center_view()->GetFocusManager()->GetFocusedView());
 
   widget->GetRootView()->RemoveChildView(message_center_view());
diff --git a/ash/system/message_center/unified_message_list_view.cc b/ash/system/message_center/unified_message_list_view.cc
index ded2a3a..efe1806 100644
--- a/ash/system/message_center/unified_message_list_view.cc
+++ b/ash/system/message_center/unified_message_list_view.cc
@@ -29,7 +29,7 @@
 namespace {
 
 constexpr base::TimeDelta kClosingAnimationDuration =
-    base::TimeDelta::FromMilliseconds(330);
+    base::TimeDelta::FromMilliseconds(320);
 constexpr base::TimeDelta kClearAllStackedAnimationDuration =
     base::TimeDelta::FromMilliseconds(40);
 constexpr base::TimeDelta kClearAllVisibleAnimationDuration =
@@ -69,8 +69,10 @@
     : public views::View,
       public MessageView::SlideObserver {
  public:
-  explicit MessageViewContainer(MessageView* message_view)
+  MessageViewContainer(MessageView* message_view,
+                       UnifiedMessageListView* list_view)
       : message_view_(message_view),
+        list_view_(list_view),
         control_view_(new NotificationSwipeControlView(message_view)) {
     message_view_->AddSlideObserver(this);
 
@@ -79,7 +81,7 @@
     AddChildView(message_view_);
   }
 
-  ~MessageViewContainer() override = default;
+  ~MessageViewContainer() override { message_view_->RemoveSlideObserver(this); }
 
   // Update the border and background corners based on if the notification is
   // at the top or the bottom.
@@ -125,6 +127,10 @@
     }
   }
 
+  void SlideOutAndClose() {
+    message_view_->SlideOutAndClose(1 /* direction */);
+  }
+
   std::string GetNotificationId() const {
     return message_view_->notification_id();
   }
@@ -160,7 +166,9 @@
   }
 
   void OnSlideOut(const std::string& notification_id) override {
-    is_slid_out_by_user_ = true;
+    is_slid_out_ = true;
+    set_is_removed();
+    list_view_->OnNotificationSlidOut();
   }
 
   gfx::Rect start_bounds() const { return start_bounds_; }
@@ -177,7 +185,7 @@
 
   void set_is_removed() { is_removed_ = true; }
 
-  bool is_slid_out_by_user() { return is_slid_out_by_user_; }
+  bool is_slid_out() { return is_slid_out_; }
 
  private:
   // The bounds that the container starts animating from. If not animating, it's
@@ -188,14 +196,14 @@
   // actual bounds().
   gfx::Rect ideal_bounds_;
 
-  // True when the notification is removed and during SLIDE_OUT animation.
-  // Unused if |state_| is not SLIDE_OUT.
+  // True when the notification is removed and during slide out animation.
   bool is_removed_ = false;
 
-  // True if the notification is slid out completely by the user.
-  bool is_slid_out_by_user_ = false;
+  // True if the notification is slid out completely.
+  bool is_slid_out_ = false;
 
   MessageView* const message_view_;
+  UnifiedMessageListView* const list_view_;
   NotificationSwipeControlView* const control_view_;
 
   DISALLOW_COPY_AND_ASSIGN(MessageViewContainer);
@@ -224,7 +232,8 @@
 void UnifiedMessageListView::Init() {
   bool is_latest = true;
   for (auto* notification : MessageCenter::Get()->GetVisibleNotifications()) {
-    auto* view = new MessageViewContainer(CreateMessageView(*notification));
+    auto* view =
+        new MessageViewContainer(CreateMessageView(*notification), this);
     view->LoadExpandedState(model_, is_latest);
     AddChildViewAt(view, 0);
     MessageCenter::Get()->DisplayedNotification(
@@ -333,7 +342,7 @@
   auto* view = CreateMessageView(*notification);
   // Expand the latest notification.
   view->SetExpanded(view->IsAutoExpandingAllowed());
-  AddChildView(new MessageViewContainer(view));
+  AddChildView(new MessageViewContainer(view, this));
   UpdateBorders();
   ResetBounds();
 }
@@ -342,18 +351,32 @@
                                                    bool by_user) {
   if (ignore_notification_remove_)
     return;
+
+  // The corresponding MessageView may have already been deleted after being
+  // manually slid out.
+  auto* child = GetNotificationById(id);
+  if (!child)
+    return;
+
   InterruptClearAll();
   ResetBounds();
 
-  auto* child = GetNotificationById(id);
-  if (child)
-    child->set_is_removed();
+  child->set_is_removed();
 
-  state_ = child->is_slid_out_by_user() ? State::MOVE_DOWN : State::SLIDE_OUT;
+  // If the MessageView is slid out, then do nothing here. The MOVE_DOWN
+  // animation will be started in OnNotificationSlidOut().
+  if (!child->is_slid_out())
+    child->SlideOutAndClose();
+}
 
-  if (child->is_slid_out_by_user())
-    DeleteRemovedNotifications();
+void UnifiedMessageListView::OnNotificationSlidOut() {
+  DeleteRemovedNotifications();
 
+  // |message_center_view_| can be null in tests.
+  if (message_center_view_)
+    message_center_view_->OnNotificationSlidOut();
+
+  state_ = State::MOVE_DOWN;
   UpdateBounds();
   StartAnimation();
 }
@@ -365,10 +388,13 @@
 
   InterruptClearAll();
 
+  // The corresponding MessageView may have been slid out and deleted, so just
+  // ignore this update as the notification will soon be deleted.
   auto* child = GetNotificationById(id);
-  if (child)
-    child->UpdateWithNotification(*notification);
+  if (!child)
+    return;
 
+  child->UpdateWithNotification(*notification);
   ResetBounds();
 }
 
@@ -388,12 +414,7 @@
   animation_->SetCurrentValue(1.0);
   PreferredSizeChanged();
 
-  if (state_ == State::SLIDE_OUT) {
-    DeleteRemovedNotifications();
-    UpdateBounds();
-
-    state_ = State::MOVE_DOWN;
-  } else if (state_ == State::MOVE_DOWN) {
+  if (state_ == State::MOVE_DOWN) {
     state_ = State::IDLE;
   } else if (state_ == State::CLEAR_ALL_STACKED ||
              state_ == State::CLEAR_ALL_VISIBLE) {
@@ -541,14 +562,7 @@
   switch (state_) {
     case State::IDLE:
       break;
-    case State::SLIDE_OUT:
-      animation_->SetDuration(kClosingAnimationDuration);
-      animation_->Start();
-      break;
     case State::MOVE_DOWN:
-      // |message_center_view_| can be null in tests.
-      if (message_center_view_)
-        message_center_view_->OnNotificationSlidOut();
       animation_->SetDuration(kClosingAnimationDuration);
       animation_->Start();
       break;
@@ -600,11 +614,10 @@
 }
 
 double UnifiedMessageListView::GetCurrentValue() const {
-  return gfx::Tween::CalculateValue(
-      state_ == State::SLIDE_OUT || state_ == State::CLEAR_ALL_VISIBLE
-          ? gfx::Tween::EASE_IN
-          : gfx::Tween::FAST_OUT_SLOW_IN,
-      animation_->GetCurrentValue());
+  return gfx::Tween::CalculateValue(state_ == State::CLEAR_ALL_VISIBLE
+                                        ? gfx::Tween::EASE_IN
+                                        : gfx::Tween::FAST_OUT_SLOW_IN,
+                                    animation_->GetCurrentValue());
 }
 
 }  // namespace ash
diff --git a/ash/system/message_center/unified_message_list_view.h b/ash/system/message_center/unified_message_list_view.h
index 5de0cdb..f2aee1b 100644
--- a/ash/system/message_center/unified_message_list_view.h
+++ b/ash/system/message_center/unified_message_list_view.h
@@ -68,6 +68,10 @@
   // Returns true if an animation is currently in progress.
   bool IsAnimating() const;
 
+  // Called when a notification is slid out so we can run the MOVE_DOWN
+  // animation.
+  void OnNotificationSlidOut();
+
   // views::View:
   void ChildPreferredSizeChanged(views::View* child) override;
   void PreferredSizeChanged() override;
@@ -113,9 +117,6 @@
     // No animation is running.
     IDLE,
 
-    // Sliding out a removed notification. It will transition to MOVE_DOWN.
-    SLIDE_OUT,
-
     // Moving down notifications.
     MOVE_DOWN,
 
diff --git a/ash/system/message_center/unified_message_list_view_unittest.cc b/ash/system/message_center/unified_message_list_view_unittest.cc
index b7d43675..ba20b8e 100644
--- a/ash/system/message_center/unified_message_list_view_unittest.cc
+++ b/ash/system/message_center/unified_message_list_view_unittest.cc
@@ -8,9 +8,11 @@
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/unified/unified_system_tray_model.h"
 #include "ash/test/ash_test_base.h"
+#include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
+#include "ui/compositor/layer_animator.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/views/message_view.h"
 #include "ui/message_center/views/notification_view_md.h"
@@ -26,7 +28,11 @@
 class TestNotificationView : public message_center::NotificationViewMD {
  public:
   TestNotificationView(const message_center::Notification& notification)
-      : NotificationViewMD(notification) {}
+      : NotificationViewMD(notification) {
+    layer()->GetAnimator()->set_preemption_strategy(
+        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+  }
+
   ~TestNotificationView() override = default;
 
   // message_center::NotificationViewMD:
@@ -139,6 +145,8 @@
     return message_list_view()->children()[index]->bounds();
   }
 
+  void FinishSlideOutAnimation() { base::RunLoop().RunUntilIdle(); }
+
   void AnimateToMiddle() {
     EXPECT_TRUE(IsAnimating());
     message_list_view()->animation_->SetCurrentValue(0.5);
@@ -164,6 +172,10 @@
 
   int size_changed_count() const { return size_changed_count_; }
 
+  ui::LayerAnimator* LayerAnimatorAt(int i) {
+    return GetMessageViewAt(i)->layer()->GetAnimator();
+  }
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
   int id_ = 0;
@@ -246,13 +258,15 @@
   CreateMessageListView();
   int previous_height = message_list_view()->GetPreferredSize().height();
 
+  EXPECT_EQ(2u, message_list_view()->children().size());
   EXPECT_EQ(kUnifiedTrayCornerRadius, GetMessageViewAt(0)->top_radius());
   EXPECT_EQ(0, GetMessageViewAt(0)->bottom_radius());
 
   gfx::Rect previous_bounds = GetMessageViewBounds(0);
   MessageCenter::Get()->RemoveNotification(id0, true /* by_user */);
+  FinishSlideOutAnimation();
   AnimateUntilIdle();
-  EXPECT_EQ(3, size_changed_count());
+  EXPECT_EQ(1u, message_list_view()->children().size());
   EXPECT_EQ(previous_bounds.y(), GetMessageViewBounds(0).y());
   EXPECT_LT(0, message_list_view()->GetPreferredSize().height());
   EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
@@ -261,8 +275,9 @@
   EXPECT_EQ(kUnifiedTrayCornerRadius, GetMessageViewAt(0)->bottom_radius());
 
   MessageCenter::Get()->RemoveNotification(id1, true /* by_user */);
+  FinishSlideOutAnimation();
   AnimateUntilIdle();
-  EXPECT_EQ(6, size_changed_count());
+  EXPECT_EQ(0u, message_list_view()->children().size());
   EXPECT_EQ(0, message_list_view()->GetPreferredSize().height());
 }
 
@@ -300,11 +315,7 @@
   gfx::Rect bounds1 = GetMessageViewBounds(1);
 
   MessageCenter::Get()->RemoveNotification(id1, true /* by_user */);
-  AnimateToMiddle();
-  gfx::Rect slided_bounds = GetMessageViewBounds(1);
-  EXPECT_LT(bounds1.x(), slided_bounds.x());
-  AnimateToEnd();
-
+  FinishSlideOutAnimation();
   AnimateToMiddle();
   EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
   previous_height = message_list_view()->GetPreferredSize().height();
@@ -315,7 +326,7 @@
   EXPECT_EQ(bounds1, GetMessageViewBounds(1));
 
   MessageCenter::Get()->RemoveNotification(id2, true /* by_user */);
-  AnimateToEnd();
+  FinishSlideOutAnimation();
   AnimateToMiddle();
   EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
   previous_height = message_list_view()->GetPreferredSize().height();
@@ -325,7 +336,7 @@
   EXPECT_EQ(bounds0, GetMessageViewBounds(0));
 
   MessageCenter::Get()->RemoveNotification(id0, true /* by_user */);
-  AnimateToEnd();
+  FinishSlideOutAnimation();
   AnimateToMiddle();
   EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
   previous_height = message_list_view()->GetPreferredSize().height();
@@ -340,6 +351,7 @@
   CreateMessageListView();
 
   MessageCenter::Get()->RemoveNotification(id0, true /* by_user */);
+  FinishSlideOutAnimation();
   EXPECT_TRUE(IsAnimating());
   AnimateToMiddle();
 
@@ -520,7 +532,7 @@
 TEST_F(UnifiedMessageListViewTest, UserSwipesAwayNotification) {
   // Show message list with two notifications.
   AddNotification();
-  AddNotification();
+  auto id1 = AddNotification();
   CreateMessageListView();
 
   // Start swiping the notification away.
@@ -531,7 +543,8 @@
 
   // Swiping away the notification should remove it both in the MessageCenter
   // and the MessageListView.
-  GetMessageViewAt(1)->OnSlideOut();
+  MessageCenter::Get()->RemoveNotification(id1, true /* by_user */);
+  FinishSlideOutAnimation();
   EXPECT_EQ(1u, MessageCenter::Get()->GetVisibleNotifications().size());
   EXPECT_EQ(1u, message_list_view()->children().size());
 
diff --git a/ash/system/model/clock_model.cc b/ash/system/model/clock_model.cc
index f8b6a05..2843e47 100644
--- a/ash/system/model/clock_model.cc
+++ b/ash/system/model/clock_model.cc
@@ -4,6 +4,7 @@
 
 #include "ash/system/model/clock_model.h"
 
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/system/model/clock_observer.h"
@@ -51,11 +52,11 @@
 }
 
 void ClockModel::ShowDateSettings() {
-  Shell::Get()->system_tray_model()->client_ptr()->ShowDateSettings();
+  Shell::Get()->system_tray_model()->client()->ShowDateSettings();
 }
 
 void ClockModel::ShowSetTimeDialog() {
-  Shell::Get()->system_tray_model()->client_ptr()->ShowSetTimeDialog();
+  Shell::Get()->system_tray_model()->client()->ShowSetTimeDialog();
 }
 
 void ClockModel::NotifyRefreshClock() {
diff --git a/ash/system/model/system_tray_model.cc b/ash/system/model/system_tray_model.cc
index 72d2b05..6a1a1eb 100644
--- a/ash/system/model/system_tray_model.cc
+++ b/ash/system/model/system_tray_model.cc
@@ -14,7 +14,7 @@
 #include "ash/system/model/update_model.h"
 #include "ash/system/model/virtual_keyboard_model.h"
 #include "ash/system/network/active_network_icon.h"
-#include "ash/system/network/tray_network_state_observer.h"
+#include "ash/system/network/tray_network_state_model.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/unified/unified_system_tray.h"
 #include "base/logging.h"
@@ -29,10 +29,10 @@
       tracing_(std::make_unique<TracingModel>()),
       update_model_(std::make_unique<UpdateModel>()),
       virtual_keyboard_(std::make_unique<VirtualKeyboardModel>()),
-      network_observer_(std::make_unique<TrayNetworkStateObserver>(connector)),
+      network_state_model_(std::make_unique<TrayNetworkStateModel>(connector)),
       active_network_icon_(
           std::make_unique<ActiveNetworkIcon>(connector,
-                                              network_observer_.get())) {}
+                                              network_state_model_.get())) {}
 
 SystemTrayModel::~SystemTrayModel() = default;
 
@@ -40,8 +40,8 @@
   bindings_.AddBinding(this, std::move(request));
 }
 
-void SystemTrayModel::SetClient(mojom::SystemTrayClientPtr client_ptr) {
-  client_ptr_ = std::move(client_ptr);
+void SystemTrayModel::SetClient(SystemTrayClient* client) {
+  client_ = client;
 }
 
 void SystemTrayModel::SetPrimaryTrayEnabled(bool enabled) {
diff --git a/ash/system/model/system_tray_model.h b/ash/system/model/system_tray_model.h
index 6051bd0..8b53810 100644
--- a/ash/system/model/system_tray_model.h
+++ b/ash/system/model/system_tray_model.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "ash/public/cpp/system_tray.h"
 #include "ash/public/interfaces/system_tray.mojom.h"
 #include "base/macros.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
@@ -22,13 +23,15 @@
 class EnterpriseDomainModel;
 class LocaleModel;
 class SessionLengthLimitModel;
+class SystemTrayClient;
 class TracingModel;
-class TrayNetworkStateObserver;
+class TrayNetworkStateModel;
 class UpdateModel;
 class VirtualKeyboardModel;
 
 // Top level model of SystemTray.
-class SystemTrayModel : public mojom::SystemTray {
+// TODO(jamescook): Eliminate mojo interface.
+class SystemTrayModel : public SystemTray, public mojom::SystemTray {
  public:
   explicit SystemTrayModel(service_manager::Connector* connector);
   ~SystemTrayModel() override;
@@ -36,8 +39,10 @@
   // Binds the mojom::SystemTray interface to this object.
   void BindRequest(mojom::SystemTrayRequest request);
 
+  // SystemTray:
+  void SetClient(SystemTrayClient* client) override;
+
   // mojom::SystemTray:
-  void SetClient(mojom::SystemTrayClientPtr client) override;
   void SetPrimaryTrayEnabled(bool enabled) override;
   void SetPrimaryTrayVisible(bool visible) override;
   void SetUse24HourClock(bool use_24_hour) override;
@@ -68,13 +73,13 @@
   TracingModel* tracing() { return tracing_.get(); }
   UpdateModel* update_model() { return update_model_.get(); }
   VirtualKeyboardModel* virtual_keyboard() { return virtual_keyboard_.get(); }
-  TrayNetworkStateObserver* network_observer() {
-    return network_observer_.get();
+  TrayNetworkStateModel* network_state_model() {
+    return network_state_model_.get();
   }
   ActiveNetworkIcon* active_network_icon() {
     return active_network_icon_.get();
   }
-  const mojom::SystemTrayClientPtr& client_ptr() { return client_ptr_; }
+  SystemTrayClient* client() { return client_; }
 
  private:
   std::unique_ptr<ClockModel> clock_;
@@ -84,7 +89,7 @@
   std::unique_ptr<TracingModel> tracing_;
   std::unique_ptr<UpdateModel> update_model_;
   std::unique_ptr<VirtualKeyboardModel> virtual_keyboard_;
-  std::unique_ptr<TrayNetworkStateObserver> network_observer_;
+  std::unique_ptr<TrayNetworkStateModel> network_state_model_;
   std::unique_ptr<ActiveNetworkIcon> active_network_icon_;
 
   // TODO(tetsui): Add following as a sub-model of SystemTrayModel:
@@ -94,7 +99,7 @@
   mojo::BindingSet<mojom::SystemTray> bindings_;
 
   // Client interface in chrome browser. May be null in tests.
-  mojom::SystemTrayClientPtr client_ptr_;
+  SystemTrayClient* client_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(SystemTrayModel);
 };
diff --git a/ash/system/network/active_network_icon.cc b/ash/system/network/active_network_icon.cc
index de2a9e7..91888ce 100644
--- a/ash/system/network/active_network_icon.cc
+++ b/ash/system/network/active_network_icon.cc
@@ -18,6 +18,7 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/paint_vector_icon.h"
 
+using chromeos::network_config::mojom::ActivationStateType;
 using chromeos::network_config::mojom::ConnectionStateType;
 using chromeos::network_config::mojom::DeviceStateProperties;
 using chromeos::network_config::mojom::DeviceStateType;
@@ -49,7 +50,7 @@
 }  // namespace
 
 ActiveNetworkIcon::ActiveNetworkIcon(service_manager::Connector* connector,
-                                     TrayNetworkStateObserver* model)
+                                     TrayNetworkStateModel* model)
     : model_(model), weak_ptr_factory_(this) {
   if (connector)  // May be null in tests.
     BindCrosNetworkConfig(connector);
@@ -91,11 +92,28 @@
       network = model_->active_cellular();
       break;
   }
-  if (network &&
-      chromeos::network_config::StateIsConnected(network->connection_state)) {
-    *a11y_name =
-        l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED,
-                                   base::UTF8ToUTF16(network->name));
+
+  base::string16 network_name;
+  if (network) {
+    network_name = network->type == NetworkType::kEthernet
+                       ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ETHERNET)
+                       : base::UTF8ToUTF16(network->name);
+  }
+  // Check for Activating first since activating networks may be connected.
+  if (network && network->type == NetworkType::kCellular &&
+      network->cellular->activation_state == ActivationStateType::kActivating) {
+    base::string16 activating_string = l10n_util::GetStringFUTF16(
+        IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING, network_name);
+    if (a11y_name)
+      *a11y_name = activating_string;
+    if (a11y_desc)
+      *a11y_desc = base::string16();
+    if (tooltip)
+      *tooltip = activating_string;
+  } else if (network && chromeos::network_config::StateIsConnected(
+                            network->connection_state)) {
+    base::string16 connected_string = l10n_util::GetStringFUTF16(
+        IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED, network_name);
     base::string16 signal_strength_string;
     if (chromeos::network_config::NetworkTypeMatchesType(
             network->type, NetworkType::kWireless)) {
@@ -120,27 +138,38 @@
           break;
       }
     }
-    *a11y_desc = signal_strength_string;
-    *tooltip =
-        signal_strength_string.empty()
-            ? *a11y_name
-            : l10n_util::GetStringFUTF16(
-                  IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED_ACCESSIBLE,
-                  base::UTF8ToUTF16(network->name), signal_strength_string);
+    if (a11y_name)
+      *a11y_name = connected_string;
+    if (a11y_desc)
+      *a11y_desc = signal_strength_string;
+    if (tooltip) {
+      *tooltip = signal_strength_string.empty()
+                     ? connected_string
+                     : l10n_util::GetStringFUTF16(
+                           IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED_TOOLTIP,
+                           network_name, signal_strength_string);
+    }
   } else if (network &&
              network->connection_state == ConnectionStateType::kConnecting) {
-    *a11y_name = l10n_util::GetStringUTF16(
-        IDS_ASH_STATUS_TRAY_NETWORK_NOT_CONNECTED_A11Y);
-    *a11y_desc = base::string16();
-    *tooltip = l10n_util::GetStringFUTF16(
-        IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING_TOOLTIP,
-        base::UTF8ToUTF16(network->name));
+    base::string16 connecting_string = l10n_util::GetStringFUTF16(
+        IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING, network_name);
+    if (a11y_name)
+      *a11y_name = connecting_string;
+    if (a11y_desc)
+      *a11y_desc = base::string16();
+    if (tooltip)
+      *tooltip = connecting_string;
   } else {
-    *a11y_name = l10n_util::GetStringUTF16(
-        IDS_ASH_STATUS_TRAY_NETWORK_NOT_CONNECTED_A11Y);
-    *a11y_desc = base::string16();
-    *tooltip = l10n_util::GetStringUTF16(
-        IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECTED_TOOLTIP);
+    if (a11y_name) {
+      *a11y_name = l10n_util::GetStringUTF16(
+          IDS_ASH_STATUS_TRAY_NETWORK_NOT_CONNECTED_A11Y);
+    }
+    if (a11y_desc)
+      *a11y_desc = base::string16();
+    if (tooltip) {
+      *tooltip = l10n_util::GetStringUTF16(
+          IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECTED_TOOLTIP);
+    }
   }
 }
 
@@ -296,7 +325,7 @@
     cellular_uninitialized_msg_ = 0;
 }
 
-// TrayNetworkStateObserver::Observer
+// TrayNetworkStateModel::Observer
 
 void ActiveNetworkIcon::ActiveNetworkStateChanged() {
   SetCellularUninitializedMsg();
diff --git a/ash/system/network/active_network_icon.h b/ash/system/network/active_network_icon.h
index 77abb4e..786e4b4 100644
--- a/ash/system/network/active_network_icon.h
+++ b/ash/system/network/active_network_icon.h
@@ -10,7 +10,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/system/network/network_icon.h"
-#include "ash/system/network/tray_network_state_observer.h"
+#include "ash/system/network/tray_network_state_model.h"
 #include "base/macros.h"
 #include "base/strings/string16.h"
 #include "base/time/time.h"
@@ -40,7 +40,7 @@
 // TODO(stevenjb): Move all test coverage to active_network_icon_unittest.cc and
 // test Dual icon methods.
 // This class is also responsible for periodically purging the icon cache.
-class ASH_EXPORT ActiveNetworkIcon : public TrayNetworkStateObserver::Observer {
+class ASH_EXPORT ActiveNetworkIcon : public TrayNetworkStateModel::Observer {
  public:
   enum class Type {
     kSingle,    // A single network icon in the tray.
@@ -49,10 +49,11 @@
   };
 
   ActiveNetworkIcon(service_manager::Connector* connector,
-                    TrayNetworkStateObserver* model);
+                    TrayNetworkStateModel* model);
   ~ActiveNetworkIcon() override;
 
-  // Provides the a11y and tooltip strings for |type|.
+  // Provides the a11y and tooltip strings for |type|. Output parameters can
+  // be null.
   void GetConnectionStatusStrings(Type type,
                                   base::string16* a11y_name,
                                   base::string16* a11y_desc,
@@ -86,13 +87,15 @@
 
   void SetCellularUninitializedMsg();
 
-  // TrayNetworkStateObserver::Observer
+  // TrayNetworkStateModel::Observer
   void ActiveNetworkStateChanged() override;
   void NetworkListChanged() override;
 
   void PurgeNetworkIconCache();
+  const chromeos::network_config::mojom::NetworkStateProperties*
+  GetNetworkForType(Type type);
 
-  TrayNetworkStateObserver* model_;
+  TrayNetworkStateModel* model_;
 
   chromeos::network_config::mojom::CrosNetworkConfigPtr
       cros_network_config_ptr_;
diff --git a/ash/system/network/active_network_icon_unittest.cc b/ash/system/network/active_network_icon_unittest.cc
index 6ac0e9a..d25f637 100644
--- a/ash/system/network/active_network_icon_unittest.cc
+++ b/ash/system/network/active_network_icon_unittest.cc
@@ -9,7 +9,7 @@
 
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/network/network_icon.h"
-#include "ash/system/network/tray_network_state_observer.h"
+#include "ash/system/network/tray_network_state_model.h"
 #include "base/message_loop/message_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
@@ -41,10 +41,10 @@
   ~ActiveNetworkIconTest() override = default;
 
   void SetUp() override {
-    network_state_observer_ = std::make_unique<TrayNetworkStateObserver>(
+    network_state_model_ = std::make_unique<TrayNetworkStateModel>(
         network_config_helper_.connector());
     active_network_icon_ = std::make_unique<ActiveNetworkIcon>(
-        network_config_helper_.connector(), network_state_observer_.get());
+        network_config_helper_.connector(), network_state_model_.get());
   }
 
   void TearDown() override { active_network_icon_.reset(); }
@@ -157,7 +157,7 @@
  private:
   const base::MessageLoop message_loop_;
   chromeos::network_config::CrosNetworkConfigTestHelper network_config_helper_;
-  std::unique_ptr<TrayNetworkStateObserver> network_state_observer_;
+  std::unique_ptr<TrayNetworkStateModel> network_state_model_;
   std::unique_ptr<ActiveNetworkIcon> active_network_icon_;
 
   std::string eth_path_;
@@ -183,7 +183,7 @@
             name);
   EXPECT_EQ(
       l10n_util::GetStringFUTF16(
-          IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED_ACCESSIBLE,
+          IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED_TOOLTIP,
           base::UTF8ToUTF16(kCellularNetworkGuid),
           l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_STRONG)),
       tooltip);
diff --git a/ash/system/network/network_feature_pod_button.cc b/ash/system/network/network_feature_pod_button.cc
index b5ff745..6292119 100644
--- a/ash/system/network/network_feature_pod_button.cc
+++ b/ash/system/network/network_feature_pod_button.cc
@@ -12,124 +12,71 @@
 #include "ash/system/network/network_icon_animation.h"
 #include "ash/system/tray/system_tray_notifier.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chromeos/network/network_state.h"
-#include "chromeos/network/network_state_handler.h"
+#include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
+#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
+#include "components/onc/onc_constants.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
 #include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
 #include "ui/base/l10n/l10n_util.h"
 
-using chromeos::NetworkConnectionHandler;
-using chromeos::NetworkHandler;
-using chromeos::NetworkState;
-using chromeos::NetworkStateHandler;
-using chromeos::NetworkTypePattern;
+using chromeos::network_config::mojom::ActivationStateType;
+using chromeos::network_config::mojom::CellularStateProperties;
+using chromeos::network_config::mojom::ConnectionStateType;
+using chromeos::network_config::mojom::DeviceStateType;
+using chromeos::network_config::mojom::NetworkStateProperties;
+using chromeos::network_config::mojom::NetworkType;
 
 namespace ash {
 
 namespace {
 
-const NetworkState* GetCurrentConnectedNetwork() {
-  NetworkStateHandler* state_handler =
-      NetworkHandler::Get()->network_state_handler();
-  const NetworkState* connected_network =
-      state_handler->ConnectedNetworkByType(NetworkTypePattern::NonVirtual());
+base::string16 GetSubLabelForConnectedNetwork(
+    const NetworkStateProperties* network) {
+  DCHECK(network &&
+         chromeos::network_config::StateIsConnected(network->connection_state));
 
-  if (!connected_network)
-    return nullptr;
-
-  // It is possible that a device type has been disabled but a network
-  // corresponding to that device has not yet been updated. If this is the case,
-  // that network should not be considered connected in this UI surface.
-  if (!state_handler->IsTechnologyEnabled(
-          NetworkTypePattern::Primitive(connected_network->type()))) {
-    return nullptr;
-  }
-
-  return connected_network;
-}
-
-bool ShouldToggleBeOn() {
-  // The toggle should always be on if Wi-Fi is enabled.
-  if (NetworkHandler::Get()->network_state_handler()->IsTechnologyEnabled(
-          NetworkTypePattern::WiFi())) {
-    return true;
-  }
-
-  // Otherwise, the toggle should be on if there is a connected network.
-  return GetCurrentConnectedNetwork() != nullptr;
-}
-
-const NetworkState* GetCurrentNetwork() {
-  NetworkStateHandler* state_handler =
-      NetworkHandler::Get()->network_state_handler();
-  const NetworkState* connected_network = GetCurrentConnectedNetwork();
-  const NetworkState* connecting_network =
-      state_handler->ConnectingNetworkByType(NetworkTypePattern::Wireless());
-
-  // If connecting to a network, and there is either no connected network or
-  // the connection was user requested, use the connecting network.
-  if (connecting_network &&
-      (!connected_network || connecting_network->connect_requested())) {
-    return connecting_network;
-  }
-
-  if (connected_network)
-    return connected_network;
-
-  // If no connecting network, check if we are activating a network.
-  const NetworkState* mobile_network =
-      state_handler->FirstNetworkByType(NetworkTypePattern::Mobile());
-  if (mobile_network && (mobile_network->activation_state() ==
-                         shill::kActivationStateActivating)) {
-    return mobile_network;
-  }
-
-  return nullptr;
-}
-
-base::string16 GetSubLabelForConnectedNetwork(const NetworkState* network) {
-  DCHECK(network && network->IsConnectedState());
-
-  if (!network->Matches(NetworkTypePattern::Wireless())) {
+  if (!chromeos::network_config::NetworkStateMatchesType(
+          network, NetworkType::kWireless)) {
     return l10n_util::GetStringUTF16(
         IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED);
   }
 
-  if (NetworkTypePattern::Cellular().MatchesType(network->type())) {
-    if (network->network_technology() == shill::kNetworkTechnology1Xrtt) {
+  if (network->type == NetworkType::kCellular) {
+    CellularStateProperties* cellular = network->cellular.get();
+    if (cellular->network_technology == onc::cellular::kTechnologyCdma1Xrtt) {
       return l10n_util::GetStringUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_ONE_X);
     }
-    if (network->network_technology() == shill::kNetworkTechnologyGsm) {
+    if (cellular->network_technology == onc::cellular::kTechnologyGsm) {
       return l10n_util::GetStringUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_GSM);
     }
-    if (network->network_technology() == shill::kNetworkTechnologyGprs) {
+    if (cellular->network_technology == onc::cellular::kTechnologyGprs) {
       return l10n_util::GetStringUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_GPRS);
     }
-    if (network->network_technology() == shill::kNetworkTechnologyEdge) {
+    if (cellular->network_technology == onc::cellular::kTechnologyEdge) {
       return l10n_util::GetStringUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_EDGE);
     }
-    if (network->network_technology() == shill::kNetworkTechnologyEvdo ||
-        network->network_technology() == shill::kNetworkTechnologyUmts) {
+    if (cellular->network_technology == onc::cellular::kTechnologyEvdo ||
+        cellular->network_technology == onc::cellular::kTechnologyUmts) {
       return l10n_util::GetStringUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_THREE_G);
     }
-    if (network->network_technology() == shill::kNetworkTechnologyHspa) {
+    if (cellular->network_technology == onc::cellular::kTechnologyHspa) {
       return l10n_util::GetStringUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_HSPA);
     }
-    if (network->network_technology() == shill::kNetworkTechnologyHspaPlus) {
+    if (cellular->network_technology == onc::cellular::kTechnologyHspaPlus) {
       return l10n_util::GetStringUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_HSPA_PLUS);
     }
-    if (network->network_technology() == shill::kNetworkTechnologyLte) {
+    if (cellular->network_technology == onc::cellular::kTechnologyLte) {
       return l10n_util::GetStringUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_LTE);
     }
-    if (network->network_technology() == shill::kNetworkTechnologyLteAdvanced) {
+    if (cellular->network_technology == onc::cellular::kTechnologyLteAdvanced) {
       return l10n_util::GetStringUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_CELLULAR_TYPE_LTE_PLUS);
     }
@@ -142,7 +89,9 @@
         IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED);
   }
 
-  switch (network_icon::GetSignalStrength(network->signal_strength())) {
+  int signal_strength =
+      chromeos::network_config::GetWirelessSignalStrength(network);
+  switch (network_icon::GetSignalStrength(signal_strength)) {
     case network_icon::SignalStrength::NONE:
       return l10n_util::GetStringUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED);
@@ -166,17 +115,15 @@
 NetworkFeaturePodButton::NetworkFeaturePodButton(
     FeaturePodControllerBase* controller)
     : FeaturePodButton(controller) {
-  // NetworkHandler can be uninitialized in unit tests.
-  if (!NetworkHandler::IsInitialized())
-    return;
-  Shell::Get()->system_tray_model()->network_observer()->AddObserver(this);
+  Shell::Get()->system_tray_model()->network_state_model()->AddObserver(this);
   ShowDetailedViewArrow();
   Update();
 }
 
 NetworkFeaturePodButton::~NetworkFeaturePodButton() {
   network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
-  Shell::Get()->system_tray_model()->network_observer()->RemoveObserver(this);
+  Shell::Get()->system_tray_model()->network_state_model()->RemoveObserver(
+      this);
 }
 
 void NetworkFeaturePodButton::NetworkIconChanged() {
@@ -202,49 +149,50 @@
   else
     network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
 
-  SetToggled(ShouldToggleBeOn());
+  TrayNetworkStateModel* model =
+      Shell::Get()->system_tray_model()->network_state_model();
+  NetworkStateProperties* network = model->default_network();
+
+  bool toggled = network || model->GetDeviceState(NetworkType::kWiFi) ==
+                                DeviceStateType::kEnabled;
+  SetToggled(toggled);
   icon_button()->SetImage(views::Button::STATE_NORMAL, image);
 
-  const NetworkState* network = GetCurrentNetwork();
-  if (!network) {
+  base::string16 network_name;
+  if (network) {
+    network_name = network->type == NetworkType::kEthernet
+                       ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ETHERNET)
+                       : base::UTF8ToUTF16(network->name);
+  }
+  // Check for Activating first since activating networks may be connected.
+  if (network && network->type == NetworkType::kCellular &&
+      network->cellular->activation_state == ActivationStateType::kActivating) {
+    SetLabel(network_name);
+    SetSubLabel(l10n_util::GetStringUTF16(
+        IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING_SUBLABEL));
+  } else if (network && chromeos::network_config::StateIsConnected(
+                            network->connection_state)) {
+    SetLabel(network_name);
+    SetSubLabel(GetSubLabelForConnectedNetwork(network));
+  } else if (network &&
+             network->connection_state == ConnectionStateType::kConnecting) {
+    SetLabel(network_name);
+    SetSubLabel(l10n_util::GetStringUTF16(
+        IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING_SUBLABEL));
+  } else {
     SetLabel(l10n_util::GetStringUTF16(
         IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECTED_LABEL));
     SetSubLabel(l10n_util::GetStringUTF16(
         IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECTED_SUBLABEL));
-    SetTooltipState(l10n_util::GetStringUTF16(
-        IDS_ASH_STATUS_TRAY_NETWORK_DISCONNECTED_TOOLTIP));
-    return;
   }
-
-  base::string16 network_name =
-      network->Matches(NetworkTypePattern::Ethernet())
-          ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ETHERNET)
-          : base::UTF8ToUTF16(network->name());
-
-  SetLabel(network_name);
-
-  if (network->IsConnectingState()) {
-    SetSubLabel(l10n_util::GetStringUTF16(
-        IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING_SUBLABEL));
-    SetTooltipState(l10n_util::GetStringFUTF16(
-        IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING_TOOLTIP, network_name));
-    return;
-  }
-
-  if (network->IsConnectedState()) {
-    SetSubLabel(GetSubLabelForConnectedNetwork(network));
-    SetTooltipState(l10n_util::GetStringFUTF16(
-        IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED_TOOLTIP, network_name));
-    return;
-  }
-
-  if (network->activation_state() == shill::kActivationStateActivating) {
-    SetSubLabel(l10n_util::GetStringUTF16(
-        IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING_SUBLABEL));
-    SetTooltipState(l10n_util::GetStringFUTF16(
-        IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING_TOOLTIP, network_name));
-    return;
-  }
+  base::string16 tooltip;
+  Shell::Get()
+      ->system_tray_model()
+      ->active_network_icon()
+      ->GetConnectionStatusStrings(ActiveNetworkIcon::Type::kSingle,
+                                   /*a11y_name=*/nullptr,
+                                   /*a11y_desc=*/nullptr, &tooltip);
+  SetTooltipState(tooltip);
 }
 
 void NetworkFeaturePodButton::SetTooltipState(
diff --git a/ash/system/network/network_feature_pod_button.h b/ash/system/network/network_feature_pod_button.h
index 52a5325..dd261b4 100644
--- a/ash/system/network/network_feature_pod_button.h
+++ b/ash/system/network/network_feature_pod_button.h
@@ -6,7 +6,7 @@
 #define ASH_SYSTEM_NETWORK_NETWORK_FEATURE_POD_BUTTON_H_
 
 #include "ash/system/network/network_icon_animation_observer.h"
-#include "ash/system/network/tray_network_state_observer.h"
+#include "ash/system/network/tray_network_state_model.h"
 #include "ash/system/unified/feature_pod_button.h"
 
 namespace ash {
@@ -15,7 +15,7 @@
 // animation to implement network connecting animation on feature pod button.
 class NetworkFeaturePodButton : public FeaturePodButton,
                                 public network_icon::AnimationObserver,
-                                public TrayNetworkStateObserver::Observer {
+                                public TrayNetworkStateModel::Observer {
  public:
   explicit NetworkFeaturePodButton(FeaturePodControllerBase* controller);
   ~NetworkFeaturePodButton() override;
@@ -23,7 +23,7 @@
   // network_icon::AnimationObserver:
   void NetworkIconChanged() override;
 
-  // TrayNetworkStateObserver::Observer:
+  // TrayNetworkStateModel::Observer:
   void ActiveNetworkStateChanged() override;
 
   // views::Button:
diff --git a/ash/system/network/network_icon_unittest.cc b/ash/system/network/network_icon_unittest.cc
index fec94c5..eb66923 100644
--- a/ash/system/network/network_icon_unittest.cc
+++ b/ash/system/network/network_icon_unittest.cc
@@ -41,10 +41,10 @@
 
   void SetUp() override {
     SetUpDefaultNetworkState();
-    network_state_observer_ = std::make_unique<TrayNetworkStateObserver>(
+    network_state_model_ = std::make_unique<TrayNetworkStateModel>(
         network_config_helper_.connector());
     active_network_icon_ = std::make_unique<ActiveNetworkIcon>(
-        network_config_helper_.connector(), network_state_observer_.get());
+        network_config_helper_.connector(), network_state_model_.get());
   }
 
   void TearDown() override {
@@ -153,7 +153,7 @@
  private:
   const base::MessageLoop message_loop_;
   chromeos::network_config::CrosNetworkConfigTestHelper network_config_helper_;
-  std::unique_ptr<TrayNetworkStateObserver> network_state_observer_;
+  std::unique_ptr<TrayNetworkStateModel> network_state_model_;
   std::unique_ptr<ActiveNetworkIcon> active_network_icon_;
 
   // Preconfigured service paths:
diff --git a/ash/system/network/network_section_header_view.cc b/ash/system/network/network_section_header_view.cc
index 9acddbf..0980b8c 100644
--- a/ash/system/network/network_section_header_view.cc
+++ b/ash/system/network/network_section_header_view.cc
@@ -5,6 +5,7 @@
 #include "ash/system/network/network_section_header_view.h"
 
 #include "ash/metrics/user_metrics_recorder.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -52,7 +53,7 @@
   const chromeos::NetworkState* cellular_network =
       NetworkHandler::Get()->network_state_handler()->FirstNetworkByType(
           NetworkTypePattern::Cellular());
-  Shell::Get()->system_tray_model()->client_ptr()->ShowNetworkSettings(
+  Shell::Get()->system_tray_model()->client()->ShowNetworkSettings(
       cellular_network ? cellular_network->guid() : std::string());
 }
 
@@ -336,7 +337,7 @@
   if (sender == join_button_) {
     Shell::Get()->metrics()->RecordUserMetricsAction(
         UMA_STATUS_AREA_NETWORK_JOIN_OTHER_CLICKED);
-    Shell::Get()->system_tray_model()->client_ptr()->ShowNetworkCreate(
+    Shell::Get()->system_tray_model()->client()->ShowNetworkCreate(
         ::onc::network_type::kWiFi);
     return;
   }
diff --git a/ash/system/network/network_state_list_detailed_view.cc b/ash/system/network/network_state_list_detailed_view.cc
index 348fab7..1ae2b12 100644
--- a/ash/system/network/network_state_list_detailed_view.cc
+++ b/ash/system/network/network_state_list_detailed_view.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 
 #include "ash/metrics/user_metrics_recorder.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -228,8 +229,6 @@
 
   if (sender == settings_button_)
     ShowSettings();
-
-  CloseBubble();
 }
 
 void NetworkStateListDetailedView::HandleViewClicked(views::View* view) {
@@ -264,7 +263,7 @@
       list_type_ == LIST_TYPE_VPN
           ? UMA_STATUS_AREA_SHOW_VPN_CONNECTION_DETAILS
           : UMA_STATUS_AREA_SHOW_NETWORK_CONNECTION_DETAILS);
-  Shell::Get()->system_tray_model()->client_ptr()->ShowNetworkSettings(
+  Shell::Get()->system_tray_model()->client()->ShowNetworkSettings(
       network ? network->guid() : std::string());
 }
 
@@ -287,7 +286,8 @@
   Shell::Get()->metrics()->RecordUserMetricsAction(
       list_type_ == LIST_TYPE_VPN ? UMA_STATUS_AREA_VPN_SETTINGS_OPENED
                                   : UMA_STATUS_AREA_NETWORK_SETTINGS_OPENED);
-  Shell::Get()->system_tray_model()->client_ptr()->ShowNetworkSettings(
+  CloseBubble();  // Deletes |this|.
+  Shell::Get()->system_tray_model()->client()->ShowNetworkSettings(
       std::string());
 }
 
diff --git a/ash/system/network/network_tray_view.cc b/ash/system/network/network_tray_view.cc
index 699bdccf..4478166 100644
--- a/ash/system/network/network_tray_view.cc
+++ b/ash/system/network/network_tray_view.cc
@@ -33,7 +33,7 @@
 
 NetworkTrayView::NetworkTrayView(Shelf* shelf, ActiveNetworkIcon::Type type)
     : TrayItemView(shelf), type_(type) {
-  Shell::Get()->system_tray_model()->network_observer()->AddObserver(this);
+  Shell::Get()->system_tray_model()->network_state_model()->AddObserver(this);
   CreateImageView();
   UpdateNetworkStateHandlerIcon();
   UpdateConnectionStatus(true /* notify_a11y */);
@@ -41,7 +41,8 @@
 
 NetworkTrayView::~NetworkTrayView() {
   network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
-  Shell::Get()->system_tray_model()->network_observer()->RemoveObserver(this);
+  Shell::Get()->system_tray_model()->network_state_model()->RemoveObserver(
+      this);
 }
 
 const char* NetworkTrayView::GetClassName() const {
diff --git a/ash/system/network/network_tray_view.h b/ash/system/network/network_tray_view.h
index 2e89cea..962ed9d 100644
--- a/ash/system/network/network_tray_view.h
+++ b/ash/system/network/network_tray_view.h
@@ -10,7 +10,7 @@
 #include "ash/session/session_observer.h"
 #include "ash/system/network/active_network_icon.h"
 #include "ash/system/network/network_icon_animation_observer.h"
-#include "ash/system/network/tray_network_state_observer.h"
+#include "ash/system/network/tray_network_state_model.h"
 #include "ash/system/tray/tray_item_view.h"
 #include "base/macros.h"
 
@@ -23,7 +23,7 @@
 class NetworkTrayView : public TrayItemView,
                         public network_icon::AnimationObserver,
                         public SessionObserver,
-                        public TrayNetworkStateObserver::Observer {
+                        public TrayNetworkStateModel::Observer {
  public:
   ~NetworkTrayView() override;
 
@@ -42,7 +42,7 @@
   // SessionObserver:
   void OnSessionStateChanged(session_manager::SessionState state) override;
 
-  // TrayNetworkStateObserver::Observer:
+  // TrayNetworkStateModel::Observer:
   void ActiveNetworkStateChanged() override;
   void NetworkListChanged() override;
 
diff --git a/ash/system/network/tray_network_state_observer.cc b/ash/system/network/tray_network_state_model.cc
similarity index 80%
rename from ash/system/network/tray_network_state_observer.cc
rename to ash/system/network/tray_network_state_model.cc
index b11eeb5..9a1705b 100644
--- a/ash/system/network/tray_network_state_observer.cc
+++ b/ash/system/network/tray_network_state_model.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/system/network/tray_network_state_observer.h"
+#include "ash/system/network/tray_network_state_model.h"
 
 #include <set>
 #include <string>
@@ -47,7 +47,7 @@
 
 namespace ash {
 
-TrayNetworkStateObserver::TrayNetworkStateObserver(
+TrayNetworkStateModel::TrayNetworkStateModel(
     service_manager::Connector* connector)
     : update_frequency_(kUpdateFrequencyMs) {
   if (ui::ScopedAnimationDurationScaleMode::duration_scale_mode() !=
@@ -58,45 +58,45 @@
     BindCrosNetworkConfig(connector);
 }
 
-TrayNetworkStateObserver::~TrayNetworkStateObserver() = default;
+TrayNetworkStateModel::~TrayNetworkStateModel() = default;
 
-void TrayNetworkStateObserver::AddObserver(Observer* observer) {
+void TrayNetworkStateModel::AddObserver(Observer* observer) {
   observer_list_.AddObserver(observer);
 }
 
-void TrayNetworkStateObserver::RemoveObserver(Observer* observer) {
+void TrayNetworkStateModel::RemoveObserver(Observer* observer) {
   observer_list_.RemoveObserver(observer);
 }
 
-DeviceStateProperties* TrayNetworkStateObserver::GetDevice(NetworkType type) {
+DeviceStateProperties* TrayNetworkStateModel::GetDevice(NetworkType type) {
   auto iter = devices_.find(type);
   if (iter == devices_.end())
     return nullptr;
   return iter->second.get();
 }
 
-DeviceStateType TrayNetworkStateObserver::GetDeviceState(NetworkType type) {
+DeviceStateType TrayNetworkStateModel::GetDeviceState(NetworkType type) {
   DeviceStateProperties* device = GetDevice(type);
   return device ? device->state : DeviceStateType::kUnavailable;
 }
 
 // CrosNetworkConfigObserver
 
-void TrayNetworkStateObserver::OnActiveNetworksChanged(
+void TrayNetworkStateModel::OnActiveNetworksChanged(
     std::vector<NetworkStatePropertiesPtr> networks) {
   UpdateActiveNetworks(std::move(networks));
   SendActiveNetworkStateChanged();
 }
 
-void TrayNetworkStateObserver::OnNetworkStateListChanged() {
+void TrayNetworkStateModel::OnNetworkStateListChanged() {
   NotifyNetworkListChanged();
 }
 
-void TrayNetworkStateObserver::OnDeviceStateListChanged() {
+void TrayNetworkStateModel::OnDeviceStateListChanged() {
   GetDeviceStateList();
 }
 
-void TrayNetworkStateObserver::BindCrosNetworkConfig(
+void TrayNetworkStateModel::BindCrosNetworkConfig(
     service_manager::Connector* connector) {
   // Ensure bindings are reset in case this is called after a failure.
   cros_network_config_observer_binding_.Close();
@@ -111,16 +111,16 @@
 
   // If the connection is lost (e.g. due to a crash), attempt to rebind it.
   cros_network_config_ptr_.set_connection_error_handler(
-      base::BindOnce(&TrayNetworkStateObserver::BindCrosNetworkConfig,
+      base::BindOnce(&TrayNetworkStateModel::BindCrosNetworkConfig,
                      base::Unretained(this), connector));
 }
 
-void TrayNetworkStateObserver::GetDeviceStateList() {
+void TrayNetworkStateModel::GetDeviceStateList() {
   cros_network_config_ptr_->GetDeviceStateList(base::BindOnce(
-      &TrayNetworkStateObserver::OnGetDeviceStateList, base::Unretained(this)));
+      &TrayNetworkStateModel::OnGetDeviceStateList, base::Unretained(this)));
 }
 
-void TrayNetworkStateObserver::OnGetDeviceStateList(
+void TrayNetworkStateModel::OnGetDeviceStateList(
     std::vector<DeviceStatePropertiesPtr> devices) {
   devices_.clear();
   for (auto& device : devices) {
@@ -133,16 +133,16 @@
   GetActiveNetworks();  // Will trigger an observer event.
 }
 
-void TrayNetworkStateObserver::GetActiveNetworks() {
+void TrayNetworkStateModel::GetActiveNetworks() {
   DCHECK(cros_network_config_ptr_);
   cros_network_config_ptr_->GetNetworkStateList(
       NetworkFilter::New(FilterType::kActive, NetworkType::kAll,
                          /*limit=*/0),
-      base::BindOnce(&TrayNetworkStateObserver::OnActiveNetworksChanged,
+      base::BindOnce(&TrayNetworkStateModel::OnActiveNetworksChanged,
                      base::Unretained(this)));
 }
 
-void TrayNetworkStateObserver::UpdateActiveNetworks(
+void TrayNetworkStateModel::UpdateActiveNetworks(
     std::vector<NetworkStatePropertiesPtr> networks) {
   active_cellular_.reset();
   active_vpn_.reset();
@@ -196,27 +196,27 @@
       GetConnectingOrConnected(connecting_non_cellular, connected_non_cellular);
 }
 
-void TrayNetworkStateObserver::NotifyNetworkListChanged() {
+void TrayNetworkStateModel::NotifyNetworkListChanged() {
   if (timer_.IsRunning())
     return;
   timer_.Start(
       FROM_HERE, base::TimeDelta::FromMilliseconds(update_frequency_),
-      base::BindRepeating(&TrayNetworkStateObserver::SendNetworkListChanged,
+      base::BindRepeating(&TrayNetworkStateModel::SendNetworkListChanged,
                           base::Unretained(this)));
 }
 
-void TrayNetworkStateObserver::SendActiveNetworkStateChanged() {
+void TrayNetworkStateModel::SendActiveNetworkStateChanged() {
   for (auto& observer : observer_list_)
     observer.ActiveNetworkStateChanged();
 }
 
-void TrayNetworkStateObserver::SendNetworkListChanged() {
+void TrayNetworkStateModel::SendNetworkListChanged() {
   for (auto& observer : observer_list_)
     observer.NetworkListChanged();
 }
 
-void TrayNetworkStateObserver::Observer::ActiveNetworkStateChanged() {}
+void TrayNetworkStateModel::Observer::ActiveNetworkStateChanged() {}
 
-void TrayNetworkStateObserver::Observer::NetworkListChanged() {}
+void TrayNetworkStateModel::Observer::NetworkListChanged() {}
 
 }  // namespace ash
diff --git a/ash/system/network/tray_network_state_observer.h b/ash/system/network/tray_network_state_model.h
similarity index 88%
rename from ash/system/network/tray_network_state_observer.h
rename to ash/system/network/tray_network_state_model.h
index 1cacf7b..94a2a5a 100644
--- a/ash/system/network/tray_network_state_observer.h
+++ b/ash/system/network/tray_network_state_model.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_SYSTEM_NETWORK_TRAY_NETWORK_STATE_OBSERVER_H_
-#define ASH_SYSTEM_NETWORK_TRAY_NETWORK_STATE_OBSERVER_H_
+#ifndef ASH_SYSTEM_NETWORK_TRAY_NETWORK_STATE_MODEL_H_
+#define ASH_SYSTEM_NETWORK_TRAY_NETWORK_STATE_MODEL_H_
 
 #include <vector>
 
@@ -21,10 +21,10 @@
 
 namespace ash {
 
-// TrayNetworkStateObserver observes the mojo interface and tracks the devices
+// TrayNetworkStateModel observes the mojo interface and tracks the devices
 // and active networks. It has UI observers that are informed when important
 // changes occur.
-class ASH_EXPORT TrayNetworkStateObserver
+class ASH_EXPORT TrayNetworkStateModel
     : public chromeos::network_config::mojom::CrosNetworkConfigObserver {
  public:
   class Observer : public base::CheckedObserver {
@@ -36,8 +36,8 @@
     virtual void NetworkListChanged();
   };
 
-  explicit TrayNetworkStateObserver(service_manager::Connector* connector);
-  ~TrayNetworkStateObserver() override;
+  explicit TrayNetworkStateModel(service_manager::Connector* connector);
+  ~TrayNetworkStateModel() override;
 
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
@@ -111,9 +111,9 @@
   chromeos::network_config::mojom::NetworkStatePropertiesPtr active_cellular_;
   chromeos::network_config::mojom::NetworkStatePropertiesPtr active_vpn_;
 
-  DISALLOW_COPY_AND_ASSIGN(TrayNetworkStateObserver);
+  DISALLOW_COPY_AND_ASSIGN(TrayNetworkStateModel);
 };
 
 }  // namespace ash
 
-#endif  // ASH_SYSTEM_NETWORK_TRAY_NETWORK_STATE_OBSERVER_H_
+#endif  // ASH_SYSTEM_NETWORK_TRAY_NETWORK_STATE_MODEL_H_
diff --git a/ash/system/network/unified_network_detailed_view_controller.cc b/ash/system/network/unified_network_detailed_view_controller.cc
index a4efe4d..f8664a2 100644
--- a/ash/system/network/unified_network_detailed_view_controller.cc
+++ b/ash/system/network/unified_network_detailed_view_controller.cc
@@ -16,11 +16,12 @@
     UnifiedSystemTrayController* tray_controller)
     : detailed_view_delegate_(
           std::make_unique<DetailedViewDelegate>(tray_controller)) {
-  Shell::Get()->system_tray_model()->network_observer()->AddObserver(this);
+  Shell::Get()->system_tray_model()->network_state_model()->AddObserver(this);
 }
 
 UnifiedNetworkDetailedViewController::~UnifiedNetworkDetailedViewController() {
-  Shell::Get()->system_tray_model()->network_observer()->RemoveObserver(this);
+  Shell::Get()->system_tray_model()->network_state_model()->RemoveObserver(
+      this);
 }
 
 views::View* UnifiedNetworkDetailedViewController::CreateView() {
diff --git a/ash/system/network/unified_network_detailed_view_controller.h b/ash/system/network/unified_network_detailed_view_controller.h
index f4e31ee..cef55b34 100644
--- a/ash/system/network/unified_network_detailed_view_controller.h
+++ b/ash/system/network/unified_network_detailed_view_controller.h
@@ -5,7 +5,7 @@
 #ifndef ASH_SYSTEM_NETWORK_UNIFIED_NETWORK_DETAILED_VIEW_CONTROLLER_H_
 #define ASH_SYSTEM_NETWORK_UNIFIED_NETWORK_DETAILED_VIEW_CONTROLLER_H_
 
-#include "ash/system/network/tray_network_state_observer.h"
+#include "ash/system/network/tray_network_state_model.h"
 #include "ash/system/unified/detailed_view_controller.h"
 
 namespace ash {
@@ -20,7 +20,7 @@
 // Controller of Network detailed view in UnifiedSystemTray.
 class UnifiedNetworkDetailedViewController
     : public DetailedViewController,
-      public TrayNetworkStateObserver::Observer {
+      public TrayNetworkStateModel::Observer {
  public:
   explicit UnifiedNetworkDetailedViewController(
       UnifiedSystemTrayController* tray_controller);
@@ -29,7 +29,7 @@
   // DetailedViewControllerBase:
   views::View* CreateView() override;
 
-  // TrayNetworkStateObserver::Observer:
+  // TrayNetworkStateModel::Observer:
   void ActiveNetworkStateChanged() override;
   void NetworkListChanged() override;
 
diff --git a/ash/system/network/unified_vpn_detailed_view_controller.cc b/ash/system/network/unified_vpn_detailed_view_controller.cc
index 1c4e699..22e7ad2 100644
--- a/ash/system/network/unified_vpn_detailed_view_controller.cc
+++ b/ash/system/network/unified_vpn_detailed_view_controller.cc
@@ -16,11 +16,12 @@
     UnifiedSystemTrayController* tray_controller)
     : detailed_view_delegate_(
           std::make_unique<DetailedViewDelegate>(tray_controller)) {
-  Shell::Get()->system_tray_model()->network_observer()->AddObserver(this);
+  Shell::Get()->system_tray_model()->network_state_model()->AddObserver(this);
 }
 
 UnifiedVPNDetailedViewController::~UnifiedVPNDetailedViewController() {
-  Shell::Get()->system_tray_model()->network_observer()->RemoveObserver(this);
+  Shell::Get()->system_tray_model()->network_state_model()->RemoveObserver(
+      this);
 }
 
 views::View* UnifiedVPNDetailedViewController::CreateView() {
diff --git a/ash/system/network/unified_vpn_detailed_view_controller.h b/ash/system/network/unified_vpn_detailed_view_controller.h
index 4cc5844..7beb8d4 100644
--- a/ash/system/network/unified_vpn_detailed_view_controller.h
+++ b/ash/system/network/unified_vpn_detailed_view_controller.h
@@ -5,7 +5,7 @@
 #ifndef ASH_SYSTEM_NETWORK_UNIFIED_VPN_DETAILED_VIEW_CONTROLLER_H_
 #define ASH_SYSTEM_NETWORK_UNIFIED_VPN_DETAILED_VIEW_CONTROLLER_H_
 
-#include "ash/system/network/tray_network_state_observer.h"
+#include "ash/system/network/tray_network_state_model.h"
 #include "ash/system/unified/detailed_view_controller.h"
 
 namespace ash {
@@ -20,7 +20,7 @@
 // Controller of VPN detailed view in UnifiedSystemTray.
 class UnifiedVPNDetailedViewController
     : public DetailedViewController,
-      public TrayNetworkStateObserver::Observer {
+      public TrayNetworkStateModel::Observer {
  public:
   explicit UnifiedVPNDetailedViewController(
       UnifiedSystemTrayController* tray_controller);
@@ -29,7 +29,7 @@
   // DetailedViewControllerBase:
   views::View* CreateView() override;
 
-  // TrayNetworkStateObserver::Observer:
+  // TrayNetworkStateModel::Observer:
   void ActiveNetworkStateChanged() override;
   void NetworkListChanged() override;
 
diff --git a/ash/system/network/vpn_feature_pod_controller.cc b/ash/system/network/vpn_feature_pod_controller.cc
index b8783fe..5739825 100644
--- a/ash/system/network/vpn_feature_pod_controller.cc
+++ b/ash/system/network/vpn_feature_pod_controller.cc
@@ -64,11 +64,12 @@
 VPNFeaturePodController::VPNFeaturePodController(
     UnifiedSystemTrayController* tray_controller)
     : tray_controller_(tray_controller) {
-  Shell::Get()->system_tray_model()->network_observer()->AddObserver(this);
+  Shell::Get()->system_tray_model()->network_state_model()->AddObserver(this);
 }
 
 VPNFeaturePodController::~VPNFeaturePodController() {
-  Shell::Get()->system_tray_model()->network_observer()->RemoveObserver(this);
+  Shell::Get()->system_tray_model()->network_state_model()->RemoveObserver(
+      this);
 }
 
 FeaturePodButton* VPNFeaturePodController::CreateButton() {
diff --git a/ash/system/network/vpn_feature_pod_controller.h b/ash/system/network/vpn_feature_pod_controller.h
index 3ed002a..ea49f737 100644
--- a/ash/system/network/vpn_feature_pod_controller.h
+++ b/ash/system/network/vpn_feature_pod_controller.h
@@ -5,7 +5,7 @@
 #ifndef ASH_SYSTEM_NETWORK_VPN_FEATURE_POD_CONTROLLER_H_
 #define ASH_SYSTEM_NETWORK_VPN_FEATURE_POD_CONTROLLER_H_
 
-#include "ash/system/network/tray_network_state_observer.h"
+#include "ash/system/network/tray_network_state_model.h"
 #include "ash/system/unified/feature_pod_controller_base.h"
 #include "base/macros.h"
 #include "base/strings/string16.h"
@@ -16,7 +16,7 @@
 
 // Controller of vpn feature pod button.
 class VPNFeaturePodController : public FeaturePodControllerBase,
-                                public TrayNetworkStateObserver::Observer {
+                                public TrayNetworkStateModel::Observer {
  public:
   VPNFeaturePodController(UnifiedSystemTrayController* tray_controller);
   ~VPNFeaturePodController() override;
@@ -26,7 +26,7 @@
   void OnIconPressed() override;
   SystemTrayItemUmaType GetUmaType() const override;
 
-  // TrayNetworkStateObserver::Observer:
+  // TrayNetworkStateModel::Observer:
   void ActiveNetworkStateChanged() override;
 
  private:
diff --git a/ash/system/network/vpn_list_view.cc b/ash/system/network/vpn_list_view.cc
index 9a77317..28efab8 100644
--- a/ash/system/network/vpn_list_view.cc
+++ b/ash/system/network/vpn_list_view.cc
@@ -9,6 +9,7 @@
 
 #include "ash/metrics/user_metrics_recorder.h"
 #include "ash/public/cpp/ash_pref_names.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
@@ -141,16 +142,16 @@
     if (vpn_provider_.provider_type == VPNProvider::THIRD_PARTY_VPN) {
       Shell::Get()->metrics()->RecordUserMetricsAction(
           UMA_STATUS_AREA_VPN_ADD_THIRD_PARTY_CLICKED);
-      Shell::Get()->system_tray_model()->client_ptr()->ShowThirdPartyVpnCreate(
+      Shell::Get()->system_tray_model()->client()->ShowThirdPartyVpnCreate(
           vpn_provider_.app_id);
     } else if (vpn_provider_.provider_type == VPNProvider::ARC_VPN) {
       // TODO(lgcheng@) Add UMA status if needed.
-      Shell::Get()->system_tray_model()->client_ptr()->ShowArcVpnCreate(
+      Shell::Get()->system_tray_model()->client()->ShowArcVpnCreate(
           vpn_provider_.app_id);
     } else {
       Shell::Get()->metrics()->RecordUserMetricsAction(
           UMA_STATUS_AREA_VPN_ADD_BUILT_IN_CLICKED);
-      Shell::Get()->system_tray_model()->client_ptr()->ShowNetworkCreate(
+      Shell::Get()->system_tray_model()->client()->ShowNetworkCreate(
           ::onc::network_type::kVPN);
     }
   }
diff --git a/ash/system/night_light/night_light_feature_pod_controller.cc b/ash/system/night_light/night_light_feature_pod_controller.cc
index 63efc2c..fe0be01 100644
--- a/ash/system/night_light/night_light_feature_pod_controller.cc
+++ b/ash/system/night_light/night_light_feature_pod_controller.cc
@@ -4,6 +4,7 @@
 
 #include "ash/system/night_light/night_light_feature_pod_controller.h"
 
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
@@ -60,8 +61,8 @@
   if (TrayPopupUtils::CanOpenWebUISettings()) {
     base::RecordAction(
         base::UserMetricsAction("StatusArea_NightLight_Settings"));
-    Shell::Get()->system_tray_model()->client_ptr()->ShowDisplaySettings();
-    tray_controller_->CloseBubble();
+    tray_controller_->CloseBubble();  // Deletes |this|.
+    Shell::Get()->system_tray_model()->client()->ShowDisplaySettings();
   }
 }
 
diff --git a/ash/system/palette/palette_tray.cc b/ash/system/palette/palette_tray.cc
index bbd911d8..9f78283 100644
--- a/ash/system/palette/palette_tray.cc
+++ b/ash/system/palette/palette_tray.cc
@@ -9,6 +9,7 @@
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/public/cpp/ash_pref_names.h"
 #include "ash/public/cpp/stylus_utils.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/root_window_controller.h"
 #include "ash/shelf/shelf.h"
@@ -128,13 +129,13 @@
       palette_tray_->RecordPaletteOptionsUsage(
           PaletteTrayOptions::PALETTE_SETTINGS_BUTTON,
           PaletteInvocationMethod::MENU);
-      Shell::Get()->system_tray_model()->client_ptr()->ShowPaletteSettings();
+      Shell::Get()->system_tray_model()->client()->ShowPaletteSettings();
       palette_tray_->HidePalette();
     } else if (sender == help_button_) {
       palette_tray_->RecordPaletteOptionsUsage(
           PaletteTrayOptions::PALETTE_HELP_BUTTON,
           PaletteInvocationMethod::MENU);
-      Shell::Get()->system_tray_model()->client_ptr()->ShowPaletteHelp();
+      Shell::Get()->system_tray_model()->client()->ShowPaletteHelp();
       palette_tray_->HidePalette();
     } else {
       NOTREACHED();
diff --git a/ash/system/power/dual_role_notification.cc b/ash/system/power/dual_role_notification.cc
index ac3acdb..9e2e9cf 100644
--- a/ash/system/power/dual_role_notification.cc
+++ b/ash/system/power/dual_role_notification.cc
@@ -7,6 +7,7 @@
 #include <set>
 
 #include "ash/public/cpp/notification_utils.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -127,10 +128,7 @@
   auto delegate =
       base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
           base::BindRepeating([]() {
-            Shell::Get()
-                ->system_tray_model()
-                ->client_ptr()
-                ->ShowPowerSettings();
+            Shell::Get()->system_tray_model()->client()->ShowPowerSettings();
           }));
 
   std::unique_ptr<Notification> notification = ash::CreateSystemNotification(
diff --git a/ash/system/screen_layout_observer.cc b/ash/system/screen_layout_observer.cc
index a22586d..efb78ee 100644
--- a/ash/system/screen_layout_observer.cc
+++ b/ash/system/screen_layout_observer.cc
@@ -12,6 +12,7 @@
 #include "ash/metrics/user_metrics_action.h"
 #include "ash/metrics/user_metrics_recorder.h"
 #include "ash/public/cpp/notification_utils.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
@@ -76,8 +77,8 @@
       UMA_STATUS_AREA_DISPLAY_NOTIFICATION_SELECTED);
   // Settings may be blocked, e.g. at the lock screen.
   if (Shell::Get()->session_controller()->ShouldEnableSettings() &&
-      Shell::Get()->system_tray_model()->client_ptr()) {
-    Shell::Get()->system_tray_model()->client_ptr()->ShowDisplaySettings();
+      Shell::Get()->system_tray_model()->client()) {
+    Shell::Get()->system_tray_model()->client()->ShowDisplaySettings();
     Shell::Get()->metrics()->RecordUserMetricsAction(
         UMA_STATUS_AREA_DISPLAY_NOTIFICATION_SHOW_SETTINGS);
   }
diff --git a/ash/system/tracing_notification_controller.cc b/ash/system/tracing_notification_controller.cc
index 481b91d..c66b342 100644
--- a/ash/system/tracing_notification_controller.cc
+++ b/ash/system/tracing_notification_controller.cc
@@ -5,6 +5,7 @@
 #include "ash/system/tracing_notification_controller.h"
 
 #include "ash/public/cpp/notification_utils.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -26,7 +27,7 @@
 void HandleNotificationClick() {
   Shell::Get()->metrics()->RecordUserMetricsAction(
       UMA_STATUS_AREA_TRACING_DEFAULT_SELECTED);
-  Shell::Get()->system_tray_model()->client_ptr()->ShowChromeSlow();
+  Shell::Get()->system_tray_model()->client()->ShowChromeSlow();
 }
 
 }  // namespace
diff --git a/ash/system/tray/tray_detailed_view.cc b/ash/system/tray/tray_detailed_view.cc
index 6a840da..cbced27 100644
--- a/ash/system/tray/tray_detailed_view.cc
+++ b/ash/system/tray/tray_detailed_view.cc
@@ -40,6 +40,7 @@
 #include "ui/views/layout/fill_layout.h"
 #include "ui/views/view_targeter.h"
 #include "ui/views/view_targeter_delegate.h"
+#include "ui/views/widget/widget.h"
 
 namespace ash {
 namespace {
@@ -442,6 +443,11 @@
 }
 
 void TrayDetailedView::CloseBubble() {
+  // Don't close again if we're already closing.
+  views::Widget* widget = GetWidget();
+  if (widget && widget->IsClosed())
+    return;
+
   delegate_->CloseBubble();
 }
 
diff --git a/ash/system/unified/top_shortcuts_view.cc b/ash/system/unified/top_shortcuts_view.cc
index 78c4309..b59e2d2 100644
--- a/ash/system/unified/top_shortcuts_view.cc
+++ b/ash/system/unified/top_shortcuts_view.cc
@@ -41,6 +41,7 @@
 
 UserAvatarButton::UserAvatarButton(views::ButtonListener* listener)
     : Button(listener) {
+  SetID(VIEW_ID_USER_AVATAR_BUTTON);
   SetLayoutManager(std::make_unique<views::FillLayout>());
   SetBorder(views::CreateEmptyBorder(kUnifiedCircularButtonFocusPadding));
   AddChildView(CreateUserAvatarView(0 /* user_index */));
diff --git a/ash/system/unified/unified_system_tray_controller.cc b/ash/system/unified/unified_system_tray_controller.cc
index 8b0187a..2741053 100644
--- a/ash/system/unified/unified_system_tray_controller.cc
+++ b/ash/system/unified/unified_system_tray_controller.cc
@@ -6,6 +6,7 @@
 
 #include "ash/metrics/user_metrics_action.h"
 #include "ash/metrics/user_metrics_recorder.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/system/accessibility/accessibility_feature_pod_controller.h"
@@ -107,7 +108,7 @@
 
 void UnifiedSystemTrayController::HandleSettingsAction() {
   Shell::Get()->metrics()->RecordUserMetricsAction(UMA_TRAY_SETTINGS);
-  Shell::Get()->system_tray_model()->client_ptr()->ShowSettings();
+  Shell::Get()->system_tray_model()->client()->ShowSettings();
 }
 
 void UnifiedSystemTrayController::HandlePowerAction() {
@@ -135,7 +136,7 @@
 void UnifiedSystemTrayController::HandleEnterpriseInfoAction() {
   UMA_HISTOGRAM_ENUMERATION("ChromeOS.SystemTray.OpenHelpPageForManaged",
                             MANAGED_TYPE_ENTERPRISE, MANAGED_TYPE_COUNT);
-  Shell::Get()->system_tray_model()->client_ptr()->ShowEnterpriseInfo();
+  Shell::Get()->system_tray_model()->client()->ShowEnterpriseInfo();
 }
 
 void UnifiedSystemTrayController::ToggleExpanded() {
diff --git a/ash/system/unified/unified_system_tray_test_api.cc b/ash/system/unified/unified_system_tray_test_api.cc
index 4c31488..40460240 100644
--- a/ash/system/unified/unified_system_tray_test_api.cc
+++ b/ash/system/unified/unified_system_tray_test_api.cc
@@ -64,7 +64,7 @@
 void UnifiedSystemTrayTestApi::ClickBubbleView(int view_id) {
   views::View* view = GetBubbleView(view_id);
   if (view && view->GetVisible()) {
-    gfx::Point cursor_location(1, 1);
+    gfx::Point cursor_location = view->GetLocalBounds().CenterPoint();
     views::View::ConvertPointToScreen(view, &cursor_location);
 
     ui::test::EventGenerator generator(GetRootWindow(view->GetWidget()));
diff --git a/ash/system/unified/user_chooser_detailed_view_controller.cc b/ash/system/unified/user_chooser_detailed_view_controller.cc
index b1be0d6..5409e19 100644
--- a/ash/system/unified/user_chooser_detailed_view_controller.cc
+++ b/ash/system/unified/user_chooser_detailed_view_controller.cc
@@ -56,15 +56,17 @@
 
   MultiProfileUMA::RecordSwitchActiveUser(
       MultiProfileUMA::SWITCH_ACTIVE_USER_BY_TRAY);
+  tray_controller_->CloseBubble();
   controller->SwitchActiveUser(
       controller->GetUserSession(user_index)->user_info.account_id);
-  tray_controller_->CloseBubble();
+  // SwitchActiveUser may delete us.
 }
 
 void UserChooserDetailedViewController::HandleAddUserAction() {
   MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY);
-  Shell::Get()->session_controller()->ShowMultiProfileLogin();
   tray_controller_->CloseBubble();
+  Shell::Get()->session_controller()->ShowMultiProfileLogin();
+  // ShowMultiProfileLogin may delete us.
 }
 
 views::View* UserChooserDetailedViewController::CreateView() {
diff --git a/ash/system/unified/user_chooser_detailed_view_controller_unittest.cc b/ash/system/unified/user_chooser_detailed_view_controller_unittest.cc
new file mode 100644
index 0000000..0adb2da
--- /dev/null
+++ b/ash/system/unified/user_chooser_detailed_view_controller_unittest.cc
@@ -0,0 +1,104 @@
+// 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 <memory>
+
+#include "ash/public/cpp/ash_view_ids.h"
+#include "ash/public/cpp/system_tray_test_api.h"
+#include "ash/root_window_controller.h"
+#include "ash/session/session_controller_impl.h"
+#include "ash/shell.h"
+#include "ash/system/unified/unified_system_tray.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/overview/overview_controller.h"
+#include "base/macros.h"
+#include "components/account_id/account_id.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+namespace {
+
+AccountId GetActiveUser() {
+  return Shell::Get()
+      ->session_controller()
+      ->GetUserSession(/*user_index=*/0)
+      ->user_info.account_id;
+}
+
+}  // namespace
+
+class UserChooserDetailedViewControllerTest : public AshTestBase {
+ public:
+  UserChooserDetailedViewControllerTest() = default;
+  ~UserChooserDetailedViewControllerTest() override = default;
+
+  // AshTestBase
+  void SetUp() override {
+    AshTestBase::SetUp();
+    tray_test_api_ = ash::SystemTrayTestApi::Create();
+    tray_test_api_->DisableAnimations();
+  }
+
+  SystemTrayTestApi* tray_test_api() { return tray_test_api_.get(); }
+
+ private:
+  std::unique_ptr<SystemTrayTestApi> tray_test_api_;
+  DISALLOW_COPY_AND_ASSIGN(UserChooserDetailedViewControllerTest);
+};
+
+TEST_F(UserChooserDetailedViewControllerTest,
+       ShowMultiProfileLoginWithOverview) {
+  // Enter ovewview mode.
+  Shell::Get()->overview_controller()->ToggleOverview();
+  ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
+
+  // Show system tray.
+  tray_test_api()->ShowBubble();
+  ASSERT_TRUE(tray_test_api()->IsTrayBubbleOpen());
+
+  // Click on user avatar button to start user adding.
+  ASSERT_TRUE(tray_test_api()->IsBubbleViewVisible(VIEW_ID_USER_AVATAR_BUTTON,
+                                                   /*open_tray=*/false));
+  tray_test_api()->ClickBubbleView(VIEW_ID_USER_AVATAR_BUTTON);
+
+  // Click on add user button to show multi profile login window.
+  ASSERT_TRUE(tray_test_api()->IsBubbleViewVisible(VIEW_ID_ADD_USER_BUTTON,
+                                                   /*open_tray=*/false));
+  tray_test_api()->ClickBubbleView(VIEW_ID_ADD_USER_BUTTON);
+}
+
+TEST_F(UserChooserDetailedViewControllerTest, SwitchUserWithOverview) {
+  // Add a secondary user.
+  const AccountId secondary_user =
+      AccountId::FromUserEmail("secondary@gmail.com");
+  GetSessionControllerClient()->AddUserSession(secondary_user.GetUserEmail());
+  ASSERT_FALSE(GetActiveUser() == secondary_user);
+
+  // Create an activatable widget.
+  std::unique_ptr<views::Widget> widget = CreateTestWidget();
+
+  // Enter overview mode.
+  Shell::Get()->overview_controller()->ToggleOverview();
+  ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
+
+  // Show system tray.
+  tray_test_api()->ShowBubble();
+  ASSERT_TRUE(tray_test_api()->IsTrayBubbleOpen());
+
+  // Click on user avatar button to select user.
+  ASSERT_TRUE(tray_test_api()->IsBubbleViewVisible(VIEW_ID_USER_AVATAR_BUTTON,
+                                                   /*open_tray=*/false));
+  tray_test_api()->ClickBubbleView(VIEW_ID_USER_AVATAR_BUTTON);
+
+  const int secondary_user_button_id = VIEW_ID_USER_ITEM_BUTTON_START + 1;
+  ASSERT_TRUE(tray_test_api()->IsBubbleViewVisible(secondary_user_button_id,
+                                                   /*open_tray=*/false));
+  tray_test_api()->ClickBubbleView(secondary_user_button_id);
+
+  // Active user is switched.
+  EXPECT_TRUE(GetActiveUser() == secondary_user);
+}
+
+}  // namespace ash
diff --git a/ash/system/unified/user_chooser_view.cc b/ash/system/unified/user_chooser_view.cc
index d9da697..57cd7f4 100644
--- a/ash/system/unified/user_chooser_view.cc
+++ b/ash/system/unified/user_chooser_view.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <string>
 
+#include "ash/public/cpp/ash_view_ids.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/session/session_controller_impl.h"
@@ -75,6 +76,7 @@
 
 AddUserButton::AddUserButton(UserChooserDetailedViewController* controller)
     : Button(this), controller_(controller) {
+  SetID(VIEW_ID_ADD_USER_BUTTON);
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::kHorizontal, gfx::Insets(kUnifiedTopShortcutSpacing),
       kUnifiedTopShortcutSpacing));
@@ -190,6 +192,9 @@
       capture_icon_(new views::ImageView),
       name_(new views::Label),
       email_(new views::Label) {
+  DCHECK_GT(VIEW_ID_USER_ITEM_BUTTON_END,
+            VIEW_ID_USER_ITEM_BUTTON_START + user_index);
+  SetID(VIEW_ID_USER_ITEM_BUTTON_START + user_index);
   auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::kHorizontal, gfx::Insets(0, kUnifiedTopShortcutSpacing),
       kUnifiedTopShortcutSpacing));
diff --git a/ash/system/update/update_notification_controller.cc b/ash/system/update/update_notification_controller.cc
index a29a565..0cec49a 100644
--- a/ash/system/update/update_notification_controller.cc
+++ b/ash/system/update/update_notification_controller.cc
@@ -5,6 +5,7 @@
 #include "ash/system/update/update_notification_controller.h"
 
 #include "ash/public/cpp/notification_utils.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -130,7 +131,7 @@
 
   if (!button_index) {
     // Notification message body clicked, which says "learn more".
-    Shell::Get()->system_tray_model()->client_ptr()->ShowAboutChromeOS();
+    Shell::Get()->system_tray_model()->client()->ShowAboutChromeOS();
     return;
   }
 
@@ -140,13 +141,13 @@
                                                            false /* by_user */);
 
   if (model_->update_required()) {
-    Shell::Get()->system_tray_model()->client_ptr()->RequestRestartForUpdate();
+    Shell::Get()->system_tray_model()->client()->RequestRestartForUpdate();
     Shell::Get()->metrics()->RecordUserMetricsAction(
         UMA_STATUS_AREA_OS_UPDATE_DEFAULT_SELECTED);
   } else {
     // Shows the about chrome OS page and checks for update after the page is
     // loaded.
-    Shell::Get()->system_tray_model()->client_ptr()->ShowAboutChromeOS();
+    Shell::Get()->system_tray_model()->client()->ShowAboutChromeOS();
   }
 }
 
diff --git a/ash/wm/desks/desk.cc b/ash/wm/desks/desk.cc
index 0adac0c..290579a 100644
--- a/ash/wm/desks/desk.cc
+++ b/ash/wm/desks/desk.cc
@@ -168,34 +168,12 @@
   {
     // Throttle notifying the observers, while we move those windows and notify
     // them only once when done.
-    base::AutoReset<bool> this_desk_throttled(&should_notify_content_changed_,
-                                              false);
-    base::AutoReset<bool> target_desk_throttled(
-        &(target_desk->should_notify_content_changed_), false);
+    auto this_desk_throttled = GetScopedNotifyContentChangedDisabler();
+    auto target_desk_throttled =
+        target_desk->GetScopedNotifyContentChangedDisabler();
 
-    for (auto* window : windows_) {
-      window->RemoveObserver(this);
-
-      // Add the window to the target desk before reparenting such that when the
-      // target desk's DeskContainerObserver notices this reparenting and calls
-      // AddWindowToDesk(), the window had already been added and there's no
-      // need to iterate over its transient hierarchy.
-      target_desk->windows_.emplace(window);
-      window->AddObserver(target_desk);
-
-      // Reparent windows to the target desk's container in the same root
-      // window. Note that `windows_` may contain transient children, which may
-      // not have the same parent as the source desk's container. So, only
-      // reparent the windows which are direct children of the source desks'
-      // container.
-      // TODO(afakhry): Check if this is necessary.
-      aura::Window* root = window->GetRootWindow();
-      aura::Window* source_container = GetDeskContainerForRoot(root);
-      aura::Window* target_container =
-          target_desk->GetDeskContainerForRoot(root);
-      if (window->parent() == source_container)
-        target_container->AddChild(window);
-    }
+    for (auto* window : windows_)
+      MoveWindowToDeskInternal(window, target_desk);
 
     windows_.clear();
   }
@@ -204,6 +182,37 @@
   target_desk->NotifyContentChanged();
 }
 
+void Desk::MoveWindowToDesk(aura::Window* window, Desk* target_desk) {
+  DCHECK(target_desk);
+  DCHECK(window);
+  DCHECK(windows_.contains(window));
+  DCHECK(this != target_desk);
+
+  {
+    // TODO(afakhry): Throttling here should not be necessary for a single
+    // window. It should be possible to rely on
+    // DeskContainerObserver::OnWindowAdded() but the problem is we don't have
+    // aura::WindowObserver::OnWindowRemoved(); we instead only have
+    // aura::WindowObserver::OnWillRemoveWindow(). This won't work as we should
+    // only notify and refresh the mini_views once the window is no longer in
+    // the tree. Two possible solutions:
+    //   - Add aura::WindowObserver::OnWindowRemoved(), or
+    //   - Remove `DeskContainerObserver` entirely and rely on
+    //     `WorkspaceLayoutManager`, which already has OnWindowAddedToLayout(),
+    //     and OnWindowRemovedFromLayout().
+    auto this_desk_throttled = GetScopedNotifyContentChangedDisabler();
+    auto target_desk_throttled =
+        target_desk->GetScopedNotifyContentChangedDisabler();
+
+    MoveWindowToDeskInternal(window, target_desk);
+
+    windows_.erase(window);
+  }
+
+  NotifyContentChanged();
+  target_desk->NotifyContentChanged();
+}
+
 aura::Window* Desk::GetDeskContainerForRoot(aura::Window* root) const {
   DCHECK(root);
 
@@ -231,4 +240,29 @@
     observer.OnContentChanged();
 }
 
+void Desk::MoveWindowToDeskInternal(aura::Window* window, Desk* target_desk) {
+  DCHECK(windows_.contains(window));
+
+  window->RemoveObserver(this);
+
+  // Add the window to the target desk before reparenting such that when the
+  // target desk's DeskContainerObserver notices this reparenting and calls
+  // AddWindowToDesk(), the window had already been added and there's no
+  // need to iterate over its transient hierarchy.
+  target_desk->windows_.insert(window);
+  window->AddObserver(target_desk);
+
+  // Reparent windows to the target desk's container in the same root
+  // window. Note that `windows_` may contain transient children, which may
+  // not have the same parent as the source desk's container. So, only
+  // reparent the windows which are direct children of the source desks'
+  // container.
+  // TODO(afakhry): Check if this is necessary.
+  aura::Window* root = window->GetRootWindow();
+  aura::Window* source_container = GetDeskContainerForRoot(root);
+  aura::Window* target_container = target_desk->GetDeskContainerForRoot(root);
+  if (window->parent() == source_container)
+    target_container->AddChild(window);
+}
+
 }  // namespace ash
diff --git a/ash/wm/desks/desk.h b/ash/wm/desks/desk.h
index 39b2c02..2e38fb2 100644
--- a/ash/wm/desks/desk.h
+++ b/ash/wm/desks/desk.h
@@ -78,9 +78,13 @@
   // on this desk will be deactivated.
   void Deactivate(bool update_window_activation);
 
-  // Moves the windows on this desk to |target_desk|.
+  // Moves all the windows on this desk to |target_desk|.
   void MoveWindowsToDesk(Desk* target_desk);
 
+  // Moves a single |window| from this desk to |target_desk|. |window| must
+  // belong to this desk.
+  void MoveWindowToDesk(aura::Window* window, Desk* target_desk);
+
   aura::Window* GetDeskContainerForRoot(aura::Window* root) const;
 
   // aura::WindowObserver:
@@ -89,6 +93,8 @@
   void NotifyContentChanged();
 
  private:
+  void MoveWindowToDeskInternal(aura::Window* window, Desk* target_desk);
+
   // The associated container ID with this desk.
   const int container_id_;
 
diff --git a/ash/wm/desks/desk_mini_view.cc b/ash/wm/desks/desk_mini_view.cc
index 773d401..0d8372b 100644
--- a/ash/wm/desks/desk_mini_view.cc
+++ b/ash/wm/desks/desk_mini_view.cc
@@ -9,6 +9,7 @@
 #include "ash/wm/desks/close_desk_button.h"
 #include "ash/wm/desks/desk.h"
 #include "ash/wm/desks/desk_preview_view.h"
+#include "ash/wm/desks/desks_bar_view.h"
 #include "ash/wm/desks/desks_controller.h"
 #include "ui/aura/window.h"
 #include "ui/views/widget/widget.h"
@@ -17,8 +18,6 @@
 
 namespace {
 
-constexpr int kDeskPreviewHeight = 64;
-
 constexpr int kLabelPreviewSpacing = 8;
 
 constexpr int kCloseButtonMargin = 4;
@@ -29,6 +28,8 @@
 
 constexpr SkColor kInactiveColor = SkColorSetARGB(0x50, 0xFF, 0xFF, 0xFF);
 
+constexpr SkColor kDraggedOverColor = SkColorSetARGB(0xFF, 0x5B, 0xBC, 0xFF);
+
 std::unique_ptr<DeskPreviewView> CreateDeskPreviewView(
     DeskMiniView* mini_view) {
   auto desk_preview_view = std::make_unique<DeskPreviewView>(mini_view);
@@ -40,8 +41,9 @@
 // which it resides, but always has a fixed height `kDeskPreviewHeight`.
 gfx::Rect GetDeskPreviewBounds(aura::Window* root_window) {
   const auto root_size = root_window->GetBoundsInRootWindow().size();
-  return gfx::Rect(kDeskPreviewHeight * root_size.width() / root_size.height(),
-                   kDeskPreviewHeight);
+  const int preview_height = DeskPreviewView::GetHeight();
+  return gfx::Rect(preview_height * root_size.width() / root_size.height(),
+                   preview_height);
 }
 
 }  // namespace
@@ -49,11 +51,12 @@
 // -----------------------------------------------------------------------------
 // DeskMiniView
 
-DeskMiniView::DeskMiniView(aura::Window* root_window,
+DeskMiniView::DeskMiniView(DesksBarView* owner_bar,
+                           aura::Window* root_window,
                            Desk* desk,
-                           const base::string16& title,
-                           views::ButtonListener* listener)
-    : views::Button(listener),
+                           const base::string16& title)
+    : views::Button(owner_bar),
+      owner_bar_(owner_bar),
       root_window_(root_window),
       desk_(desk),
       desk_preview_(CreateDeskPreviewView(this)),
@@ -81,7 +84,7 @@
   SetFocusPainter(nullptr);
   SetInkDropMode(InkDropMode::OFF);
 
-  UpdateActivationState();
+  UpdateBorderColor();
 
   SchedulePaint();
 }
@@ -104,16 +107,23 @@
 
 void DeskMiniView::OnHoverStateMayHaveChanged() {
   // TODO(afakhry): In tablet mode, discuss showing the close button on long
-  // press. Also, don't show the close button when hovered while window drag is
-  // in progress.
+  // press.
+  // Don't show the close button when hovered while the dragged window is on
+  // the DesksBarView.
   close_desk_button_->SetVisible(DesksController::Get()->CanRemoveDesks() &&
+                                 !owner_bar_->dragged_item_over_bar() &&
                                  IsMouseHovered());
 }
 
-void DeskMiniView::UpdateActivationState() {
+void DeskMiniView::UpdateBorderColor() {
   DCHECK(desk_);
-  desk_preview_->SetBorderColor(desk_->is_active() ? kActiveColor
-                                                   : kInactiveColor);
+  if (owner_bar_->dragged_item_over_bar() &&
+      IsPointOnMiniView(owner_bar_->last_dragged_item_screen_location())) {
+    desk_preview_->SetBorderColor(kDraggedOverColor);
+  } else {
+    desk_preview_->SetBorderColor(desk_->is_active() ? kActiveColor
+                                                     : kInactiveColor);
+  }
 }
 
 const char* DeskMiniView::GetClassName() const {
@@ -196,4 +206,10 @@
   // No need to remove `this` as an observer; it's done automatically.
 }
 
+bool DeskMiniView::IsPointOnMiniView(const gfx::Point& screen_location) const {
+  gfx::Point point_in_view = screen_location;
+  ConvertPointFromScreen(this, &point_in_view);
+  return HitTestPoint(point_in_view);
+}
+
 }  // namespace ash
diff --git a/ash/wm/desks/desk_mini_view.h b/ash/wm/desks/desk_mini_view.h
index 6bb57f8..28823c8 100644
--- a/ash/wm/desks/desk_mini_view.h
+++ b/ash/wm/desks/desk_mini_view.h
@@ -16,6 +16,7 @@
 namespace ash {
 
 class CloseDeskButton;
+class DesksBarView;
 class DeskPreviewView;
 
 // A view that acts as a mini representation (a.k.a. desk thumbnail) of a
@@ -26,15 +27,15 @@
                                 public views::ButtonListener,
                                 public Desk::Observer {
  public:
-  DeskMiniView(aura::Window* root_window,
+  DeskMiniView(DesksBarView* owner_bar,
+               aura::Window* root_window,
                Desk* desk,
-               const base::string16& title,
-               views::ButtonListener* listener);
+               const base::string16& title);
   ~DeskMiniView() override;
 
   aura::Window* root_window() { return root_window_; }
 
-  const Desk* desk() const { return desk_; }
+  Desk* desk() { return desk_; }
 
   const CloseDeskButton* close_desk_button() const {
     return close_desk_button_;
@@ -52,7 +53,7 @@
 
   // Updates the border color of the DeskPreviewView based on the activation
   // state of the corresponding desk.
-  void UpdateActivationState();
+  void UpdateBorderColor();
 
   // views::Button:
   const char* GetClassName() const override;
@@ -66,7 +67,11 @@
   void OnContentChanged() override;
   void OnDeskDestroyed(const Desk* desk) override;
 
+  bool IsPointOnMiniView(const gfx::Point& screen_location) const;
+
  private:
+  DesksBarView* const owner_bar_;
+
   // The root window on which this mini_view is created.
   aura::Window* root_window_;
 
diff --git a/ash/wm/desks/desk_preview_view.cc b/ash/wm/desks/desk_preview_view.cc
index e3e32c0..f4c44e5 100644
--- a/ash/wm/desks/desk_preview_view.cc
+++ b/ash/wm/desks/desk_preview_view.cc
@@ -17,6 +17,10 @@
 
 namespace {
 
+// The height of the preview view in dips.
+// TODO(afakhry): Change the height to be dynamic according to the new specs.
+constexpr int kDeskPreviewHeight = 64;
+
 // The desk preview border size in dips.
 constexpr int kBorderSize = 2;
 
@@ -61,6 +65,7 @@
   // TODO(afakhry): See if we need to avoid this in certain cases.
   mirror->SetVisible(true);
   mirror->SetOpacity(1);
+  mirror->set_sync_bounds_with_source(true);
 
   if (layer_data.should_reset_transform)
     mirror->SetTransform(gfx::Transform());
@@ -137,6 +142,11 @@
 
 DeskPreviewView::~DeskPreviewView() = default;
 
+// static
+int DeskPreviewView::GetHeight() {
+  return kDeskPreviewHeight;
+}
+
 void DeskPreviewView::SetBorderColor(SkColor color) {
   background_view_->layer()->SetColor(color);
 }
diff --git a/ash/wm/desks/desk_preview_view.h b/ash/wm/desks/desk_preview_view.h
index 130032c..941773a 100644
--- a/ash/wm/desks/desk_preview_view.h
+++ b/ash/wm/desks/desk_preview_view.h
@@ -65,6 +65,9 @@
   explicit DeskPreviewView(DeskMiniView* mini_view);
   ~DeskPreviewView() override;
 
+  // Returns the height of the DeskPreviewView.
+  static int GetHeight();
+
   void SetBorderColor(SkColor color);
 
   // This should be called when there is a change in the desk contents so that
diff --git a/ash/wm/desks/desks_bar_view.cc b/ash/wm/desks/desks_bar_view.cc
index cd750b5..344a15b 100644
--- a/ash/wm/desks/desks_bar_view.cc
+++ b/ash/wm/desks/desks_bar_view.cc
@@ -150,6 +150,19 @@
     mini_view->OnHoverStateMayHaveChanged();
 }
 
+void DesksBarView::SetDragDetails(const gfx::Point& screen_location,
+                                  bool dragged_item_over_bar) {
+  last_dragged_item_screen_location_ = screen_location;
+  const bool old_dragged_item_over_bar = dragged_item_over_bar_;
+  dragged_item_over_bar_ = dragged_item_over_bar;
+
+  if (!old_dragged_item_over_bar && !dragged_item_over_bar)
+    return;
+
+  for (auto& mini_view : mini_views_)
+    mini_view->UpdateBorderColor();
+}
+
 const char* DesksBarView::GetClassName() const {
   return "DesksBarView";
 }
@@ -253,7 +266,7 @@
   for (auto& mini_view : mini_views_) {
     const Desk* desk = mini_view->desk();
     if (desk == activated || desk == deactivated)
-      mini_view->UpdateActivationState();
+      mini_view->UpdateBorderColor();
   }
 }
 
@@ -290,7 +303,7 @@
   for (const auto& desk : desks) {
     if (!FindMiniViewForDesk(desk.get())) {
       mini_views_.emplace_back(std::make_unique<DeskMiniView>(
-          root_window, desk.get(), GetMiniViewTitle(mini_views_.size()), this));
+          this, root_window, desk.get(), GetMiniViewTitle(mini_views_.size())));
       DeskMiniView* mini_view = mini_views_.back().get();
       mini_view->set_owned_by_client();
       new_mini_views.emplace_back(mini_view);
diff --git a/ash/wm/desks/desks_bar_view.h b/ash/wm/desks/desks_bar_view.h
index 3ba13d0..b6f45b1 100644
--- a/ash/wm/desks/desks_bar_view.h
+++ b/ash/wm/desks/desks_bar_view.h
@@ -46,6 +46,12 @@
     return mini_views_;
   }
 
+  const gfx::Point& last_dragged_item_screen_location() const {
+    return last_dragged_item_screen_location_;
+  }
+
+  bool dragged_item_over_bar() const { return dragged_item_over_bar_; }
+
   // Initializes and creates mini_views for any pre-existing desks, before the
   // bar was created. This should only be called after this view has been added
   // to a widget, as it needs to call `GetWidget()` when it's performing a
@@ -55,6 +61,12 @@
   // Updates the visibility state of the close buttons on all the mini_views.
   void OnHoverStateMayHaveChanged();
 
+  // Called when an item is being dragged in overview mode to update whether it
+  // is currently intersecting with this view, and the |screen_location| of the
+  // current drag position.
+  void SetDragDetails(const gfx::Point& screen_location,
+                      bool dragged_item_over_bar);
+
   // views::View:
   const char* GetClassName() const override;
   void Layout() override;
@@ -107,6 +119,14 @@
   // mini_views accordingly.
   std::unique_ptr<DeskBarHoverObserver> hover_observer_;
 
+  // The screen location of the most recent drag position. This value is valid
+  // only when the below `dragged_item_on_bar_` is true.
+  gfx::Point last_dragged_item_screen_location_;
+
+  // True when the drag location of the overview item is intersecting with this
+  // view.
+  bool dragged_item_over_bar_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(DesksBarView);
 };
 
diff --git a/ash/wm/desks/desks_controller.cc b/ash/wm/desks/desks_controller.cc
index 3a11692..8ee5993 100644
--- a/ash/wm/desks/desks_controller.cc
+++ b/ash/wm/desks/desks_controller.cc
@@ -234,6 +234,15 @@
     animator->TakeStartingDeskScreenshot();
 }
 
+void DesksController::MoveWindowFromActiveDeskTo(aura::Window* window,
+                                                 Desk* target_desk) {
+  DCHECK(active_desk_->windows().contains(window));
+  DCHECK_NE(active_desk_, target_desk);
+
+  base::AutoReset<bool> in_progress(&are_desks_being_modified_, true);
+  active_desk_->MoveWindowToDesk(window, target_desk);
+}
+
 void DesksController::OnRootWindowAdded(aura::Window* root_window) {
   for (auto& desk : desks_)
     desk->OnRootWindowAdded(root_window);
diff --git a/ash/wm/desks/desks_controller.h b/ash/wm/desks/desks_controller.h
index ce64184..427c80d 100644
--- a/ash/wm/desks/desks_controller.h
+++ b/ash/wm/desks/desks_controller.h
@@ -90,6 +90,10 @@
   // desk will be activated.
   void ActivateDesk(const Desk* desk);
 
+  // Moves |window| (which must belong to the currently active desk) to
+  // |target_desk| (which must be a different desk).
+  void MoveWindowFromActiveDeskTo(aura::Window* window, Desk* target_desk);
+
   // Called explicitly by the RootWindowController when a root window has been
   // added or about to be removed in order to update all the available desks.
   void OnRootWindowAdded(aura::Window* root_window);
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index b18dc75..c943603 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -18,6 +18,7 @@
 #include "ash/wm/mru_window_tracker.h"
 #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_session.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
@@ -83,6 +84,22 @@
   event_generator->ClickLeftButton();
 }
 
+void DragItemToPoint(OverviewItem* item,
+                     const gfx::Point& screen_location,
+                     ui::test::EventGenerator* event_generator) {
+  DCHECK(item);
+
+  const gfx::Point item_center =
+      gfx::ToRoundedPoint(item->target_bounds().CenterPoint());
+  event_generator->MoveMouseTo(item_center);
+  event_generator->PressLeftButton();
+  // Move the mouse by an enough amount in X to engage in the normal drag mode
+  // rather than the drag to close mode.
+  event_generator->MoveMouseBy(50, 0);
+  event_generator->MoveMouseTo(screen_location);
+  event_generator->ReleaseLeftButton();
+}
+
 // Defines an observer to test DesksController notifications.
 class TestObserver : public DesksController::Observer {
  public:
@@ -572,7 +589,7 @@
   // Activate desk_4 (last one on the right) by clicking on its mini view.
   const Desk* desk_4 = controller->desks()[3].get();
   EXPECT_FALSE(desk_4->is_active());
-  const auto* mini_view = desks_bar_view->mini_views().back().get();
+  auto* mini_view = desks_bar_view->mini_views().back().get();
   EXPECT_EQ(desk_4, mini_view->desk());
   EXPECT_FALSE(mini_view->close_desk_button()->GetVisible());
   const gfx::Point mini_view_center =
@@ -688,7 +705,7 @@
   ASSERT_TRUE(desks_bar_view);
   ASSERT_EQ(4u, desks_bar_view->mini_views().size());
   Desk* desk_1 = controller->desks()[0].get();
-  const auto* mini_view = desks_bar_view->mini_views().front().get();
+  auto* mini_view = desks_bar_view->mini_views().front().get();
   EXPECT_EQ(desk_1, mini_view->desk());
 
   // Setup observers of both the active and inactive desks to make sure
@@ -764,7 +781,7 @@
   const auto* desks_bar_view = overview_grid->GetDesksBarViewForTesting();
   ASSERT_TRUE(desks_bar_view);
   ASSERT_EQ(2u, desks_bar_view->mini_views().size());
-  const auto* mini_view = desks_bar_view->mini_views().back().get();
+  auto* mini_view = desks_bar_view->mini_views().back().get();
   EXPECT_EQ(desk_2, mini_view->desk());
 
   // Setup observers of both the active and inactive desks to make sure
@@ -866,6 +883,106 @@
   EXPECT_NE(win0.get(), wm::GetActiveWindow());
 }
 
+TEST_F(DesksTest, DragWindowToDesk) {
+  auto* controller = DesksController::Get();
+  controller->NewDesk();
+  ASSERT_EQ(2u, controller->desks().size());
+  const Desk* desk_1 = controller->desks()[0].get();
+  const Desk* desk_2 = controller->desks()[1].get();
+
+  auto window = CreateTestWindow(gfx::Rect(0, 0, 250, 100));
+  wm::ActivateWindow(window.get());
+  EXPECT_EQ(window.get(), wm::GetActiveWindow());
+
+  auto* overview_controller = Shell::Get()->overview_controller();
+  overview_controller->ToggleOverview();
+  EXPECT_TRUE(overview_controller->InOverviewSession());
+  const auto* overview_grid =
+      GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
+  EXPECT_EQ(1u, overview_grid->size());
+
+  auto* overview_session = overview_controller->overview_session();
+  auto* overview_item =
+      overview_session->GetOverviewItemForWindow(window.get());
+  ASSERT_TRUE(overview_item);
+  const gfx::RectF target_bounds_before_drag = overview_item->target_bounds();
+
+  const auto* desks_bar_view = overview_grid->GetDesksBarViewForTesting();
+  ASSERT_TRUE(desks_bar_view);
+  ASSERT_EQ(2u, desks_bar_view->mini_views().size());
+  auto* desk_1_mini_view = desks_bar_view->mini_views()[0].get();
+  EXPECT_EQ(desk_1, desk_1_mini_view->desk());
+  // Drag it and drop it on its same desk's mini_view. Nothing happens, it
+  // should be returned back to its original target bounds.
+  DragItemToPoint(overview_item,
+                  desk_1_mini_view->GetBoundsInScreen().CenterPoint(),
+                  GetEventGenerator());
+  EXPECT_TRUE(overview_controller->InOverviewSession());
+  EXPECT_EQ(1u, overview_grid->size());
+  EXPECT_EQ(target_bounds_before_drag, overview_item->target_bounds());
+  EXPECT_TRUE(DoesActiveDeskContainWindow(window.get()));
+
+  // Now drag it to desk_2's mini_view. The overview grid should now show the
+  // "no-windows" widget, and the window should move to desk_2.
+  auto* desk_2_mini_view = desks_bar_view->mini_views()[1].get();
+  EXPECT_EQ(desk_2, desk_2_mini_view->desk());
+  DragItemToPoint(overview_item,
+                  desk_2_mini_view->GetBoundsInScreen().CenterPoint(),
+                  GetEventGenerator());
+  EXPECT_TRUE(overview_controller->InOverviewSession());
+  EXPECT_TRUE(overview_grid->empty());
+  EXPECT_FALSE(DoesActiveDeskContainWindow(window.get()));
+  EXPECT_TRUE(overview_session->no_windows_widget_for_testing());
+  EXPECT_TRUE(desk_2->windows().contains(window.get()));
+}
+
+TEST_F(DesksTest, DragWindowToNonMiniViewPoints) {
+  auto* controller = DesksController::Get();
+  controller->NewDesk();
+  ASSERT_EQ(2u, controller->desks().size());
+
+  auto window = CreateTestWindow(gfx::Rect(0, 0, 250, 100));
+  wm::ActivateWindow(window.get());
+  EXPECT_EQ(window.get(), wm::GetActiveWindow());
+
+  auto* overview_controller = Shell::Get()->overview_controller();
+  overview_controller->ToggleOverview();
+  EXPECT_TRUE(overview_controller->InOverviewSession());
+  const auto* overview_grid =
+      GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
+  EXPECT_EQ(1u, overview_grid->size());
+
+  auto* overview_session = overview_controller->overview_session();
+  auto* overview_item =
+      overview_session->GetOverviewItemForWindow(window.get());
+  ASSERT_TRUE(overview_item);
+  const gfx::RectF target_bounds_before_drag = overview_item->target_bounds();
+
+  const auto* desks_bar_view = overview_grid->GetDesksBarViewForTesting();
+  ASSERT_TRUE(desks_bar_view);
+
+  // Drag it and drop it on the new desk button. Nothing happens, it should be
+  // returned back to its original target bounds.
+  DragItemToPoint(
+      overview_item,
+      desks_bar_view->new_desk_button()->GetBoundsInScreen().CenterPoint(),
+      GetEventGenerator());
+  EXPECT_TRUE(overview_controller->InOverviewSession());
+  EXPECT_EQ(1u, overview_grid->size());
+  EXPECT_EQ(target_bounds_before_drag, overview_item->target_bounds());
+  EXPECT_TRUE(DoesActiveDeskContainWindow(window.get()));
+
+  // Drag it and drop it on the bottom right corner of the display. Also,
+  // nothing should happen.
+  DragItemToPoint(overview_item,
+                  window->GetRootWindow()->GetBoundsInScreen().bottom_right(),
+                  GetEventGenerator());
+  EXPECT_TRUE(overview_controller->InOverviewSession());
+  EXPECT_EQ(1u, overview_grid->size());
+  EXPECT_EQ(target_bounds_before_drag, overview_item->target_bounds());
+  EXPECT_TRUE(DoesActiveDeskContainWindow(window.get()));
+}
+
 // TODO(afakhry): Add more tests:
 // - Always on top windows are not tracked by any desk.
 // - Reusing containers when desks are removed and created.
diff --git a/ash/wm/drag_window_controller.cc b/ash/wm/drag_window_controller.cc
index 490f3a1..9562d08 100644
--- a/ash/wm/drag_window_controller.cc
+++ b/ash/wm/drag_window_controller.cc
@@ -24,6 +24,7 @@
 #include "ui/compositor/paint_context.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/display/display.h"
+#include "ui/gfx/transform_util.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
 #include "ui/wm/core/coordinate_conversion.h"
@@ -71,6 +72,7 @@
     aura::Window::ConvertRectToTarget(original_window->parent(),
                                       drag_window_->parent(), &bounds);
     drag_window_->SetBounds(bounds);
+    drag_window_->SetTransform(original_window->transform());
     SetOpacity(original_window, opacity);
   }
 
@@ -118,6 +120,7 @@
     gfx::Rect layer_bounds = layer_owner_->root()->bounds();
     layer_bounds.set_origin(gfx::Point(0, 0));
     layer_owner_->root()->SetBounds(layer_bounds);
+    layer_owner_->root()->SetTransform(gfx::Transform());
     layer_owner_->root()->SetVisible(false);
   }
 
@@ -181,10 +184,14 @@
 
   gfx::Rect dragged_window_bounds = dragged_window->bounds();
   ::wm::ConvertRectToScreen(dragged_window->parent(), &dragged_window_bounds);
-  gfx::Rect visible_bounds = root_bounds;
-  visible_bounds.Intersect(dragged_window_bounds);
+  gfx::RectF transformed_dragged_window_bounds(dragged_window_bounds);
+  gfx::TransformAboutPivot(dragged_window_bounds.origin(),
+                           dragged_window->transform())
+      .TransformRect(&transformed_dragged_window_bounds);
+  gfx::RectF visible_bounds(root_bounds);
+  visible_bounds.Intersect(transformed_dragged_window_bounds);
   return kDragPhantomMaxOpacity * visible_bounds.size().GetArea() /
-         dragged_window_bounds.size().GetArea();
+         transformed_dragged_window_bounds.size().GetArea();
 }
 
 DragWindowController::DragWindowController(aura::Window* window)
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index e6cdccb..8fefb6c 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -22,6 +22,7 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_constants.h"
 #include "ash/shell.h"
+#include "ash/wm/desks/desk_mini_view.h"
 #include "ash/wm/desks/desks_bar_view.h"
 #include "ash/wm/overview/cleanup_animation_observer.h"
 #include "ash/wm/overview/drop_target_view.h"
@@ -40,6 +41,9 @@
 #include "ash/wm/tablet_mode/tablet_mode_window_state.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
+#include "ash/wm/workspace/backdrop_controller.h"
+#include "ash/wm/workspace/workspace_layout_manager.h"
+#include "ash/wm/workspace_controller.h"
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/i18n/string_search.h"
 #include "base/numerics/ranges.h"
@@ -51,7 +55,6 @@
 #include "ui/compositor_extra/shadow.h"
 #include "ui/gfx/geometry/safe_integer_conversions.h"
 #include "ui/gfx/geometry/vector2d.h"
-#include "ui/gfx/geometry/vector2d_conversions.h"
 #include "ui/views/background.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
@@ -1068,12 +1071,27 @@
     gfx::Rect src_bounds_temp =
         minimized ? gfx::Rect()
                   : items[i]->GetWindow()->GetBoundsInRootWindow();
-    // On exiting overview, |GetBoundsInRootWindow| will have the overview
-    // translation applied to it, so undo it to get the true target bounds.
-    if (!src_bounds_temp.IsEmpty() && transition == OverviewTransition::kExit) {
-      const gfx::Vector2dF offset =
-          -items[i]->GetWindow()->transform().To2dTranslation();
-      src_bounds_temp.Offset(gfx::ToCeiledVector2d(offset));
+    if (!src_bounds_temp.IsEmpty()) {
+      if (transition == OverviewTransition::kEnter &&
+          Shell::Get()
+              ->tablet_mode_controller()
+              ->IsTabletModeWindowManagerEnabled()) {
+        BackdropController* backdrop_controller =
+            GetActiveWorkspaceController(root_window_)
+                ->layout_manager()
+                ->backdrop_controller();
+        if (backdrop_controller->GetTopmostWindowWithBackdrop() ==
+            items[i]->GetWindow()) {
+          src_bounds_temp = screen_util::GetDisplayWorkAreaBoundsInParent(
+              items[i]->GetWindow());
+        }
+      } else if (transition == OverviewTransition::kExit) {
+        // On exiting overview, |GetBoundsInRootWindow()| will have the overview
+        // translation applied to it, so use |bounds()| and
+        // |ConvertRectToScreen()| to get the true target bounds.
+        src_bounds_temp = items[i]->GetWindow()->bounds();
+        ::wm::ConvertRectToScreen(items[i]->root_window(), &src_bounds_temp);
+      }
     }
     SkIRect src_bounds = gfx::RectToSkIRect(src_bounds_temp);
     SkIRect dst_bounds = gfx::RectToSkIRect(gfx::ToEnclosedRect(
@@ -1297,6 +1315,54 @@
          (desks_bar_view_ && !desks_bar_view_->mini_views().empty());
 }
 
+bool OverviewGrid::UpdateDesksBarDragDetails(
+    const gfx::Point& screen_location) {
+  DCHECK(features::IsVirtualDesksEnabled());
+
+  const bool dragged_item_over_bar =
+      desks_widget_->GetWindowBoundsInScreen().Contains(screen_location);
+  desks_bar_view_->SetDragDetails(screen_location, dragged_item_over_bar);
+  return dragged_item_over_bar;
+}
+
+bool OverviewGrid::MaybeDropItemOnDeskMiniView(
+    const gfx::Point& screen_location,
+    OverviewItem* drag_item) {
+  DCHECK(features::IsVirtualDesksEnabled());
+
+  // End the drag for the DesksBarView.
+  desks_bar_view_->SetDragDetails(screen_location,
+                                  /*dragged_item_over_bar=*/false);
+
+  if (!desks_widget_->GetWindowBoundsInScreen().Contains(screen_location))
+    return false;
+
+  auto* desks_controller = DesksController::Get();
+  for (const auto& mini_view : desks_bar_view_->mini_views()) {
+    if (!mini_view->IsPointOnMiniView(screen_location))
+      continue;
+
+    aura::Window* const dragged_window = drag_item->GetWindow();
+    Desk* const target_desk = mini_view->desk();
+    if (target_desk == desks_controller->active_desk())
+      return false;
+
+    // TODO(afakhry): Discuss whether we should restore a minimized window when
+    // dragged and dropped in a different desk.
+
+    desks_controller->MoveWindowFromActiveDeskTo(dragged_window, target_desk);
+    // Restore the dragged item window, so that its transform is reset to
+    // identity.
+    drag_item->RestoreWindow(/*reset_transform=*/true);
+
+    // The item no longer needs to be in the overview grid.
+    overview_session_->RemoveItem(drag_item);
+    return true;
+  }
+
+  return false;
+}
+
 void OverviewGrid::MaybeInitDesksWidget() {
   if (!features::IsVirtualDesksEnabled() || desks_widget_)
     return;
diff --git a/ash/wm/overview/overview_grid.h b/ash/wm/overview/overview_grid.h
index ee3a022..c4c2911 100644
--- a/ash/wm/overview/overview_grid.h
+++ b/ash/wm/overview/overview_grid.h
@@ -255,6 +255,19 @@
   // show them once it is created).
   bool IsDesksBarViewActive() const;
 
+  // Called when a window is being dragged in Overview Mode to update the drag
+  // details (screen_location, and whether that location intersects with the
+  // desks bar widget.
+  // Returns true if |screen_location| does intersect with the DesksBarView.
+  bool UpdateDesksBarDragDetails(const gfx::Point& screen_location);
+
+  // Updates the drag details for DesksBarView to end the drag and move the
+  // window of |drag_item| to another desk if it was dropped on a mini_view of
+  // a desk that is different than that of the active desk.
+  // Returns true if the window was successfully moved to another desk.
+  bool MaybeDropItemOnDeskMiniView(const gfx::Point& screen_location,
+                                   OverviewItem* drag_item);
+
   // Returns true if the grid has no more windows.
   bool empty() const { return window_list_.empty(); }
 
@@ -281,9 +294,7 @@
 
   void set_suspend_reposition(bool value) { suspend_reposition_ = value; }
 
-  views::Widget* drop_target_widget_for_testing() {
-    return drop_target_widget_.get();
-  }
+  views::Widget* drop_target_widget() { return drop_target_widget_.get(); }
 
   const DesksBarView* GetDesksBarViewForTesting() const {
     return desks_bar_view_;
diff --git a/ash/wm/overview/overview_grid_unittest.cc b/ash/wm/overview/overview_grid_unittest.cc
index 00db08f..602f95e 100644
--- a/ash/wm/overview/overview_grid_unittest.cc
+++ b/ash/wm/overview/overview_grid_unittest.cc
@@ -8,13 +8,18 @@
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/overview/overview_item.h"
+#include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/window_state.h"
+#include "ash/wm/workspace/backdrop_controller.h"
+#include "ash/wm/workspace/workspace_layout_manager.h"
+#include "ash/wm/workspace_controller.h"
 #include "base/strings/string_number_conversions.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
 #include "ui/display/display.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/screen.h"
+#include "ui/wm/core/window_util.h"
 
 namespace ash {
 
@@ -185,4 +190,32 @@
                        base::make_optional(2u));
 }
 
+TEST_F(OverviewGridTest, WindowWithBackdrop) {
+  // Create one non resizable window and one normal window and verify that the
+  // backdrop shows over the non resizable window, and that normal window
+  // becomes maximized upon entering tablet mode.
+  auto window1 = CreateTestWindow(gfx::Rect(100, 100));
+  auto window2 = CreateTestWindow(gfx::Rect(400, 400));
+  window1->SetProperty(aura::client::kResizeBehaviorKey,
+                       ws::mojom::kResizeBehaviorNone);
+  ::wm::ActivateWindow(window1.get());
+
+  Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
+  BackdropController* backdrop_controller =
+      GetWorkspaceControllerForContext(window1.get())
+          ->layout_manager()
+          ->backdrop_controller();
+  EXPECT_EQ(window1.get(), backdrop_controller->GetTopmostWindowWithBackdrop());
+  EXPECT_TRUE(backdrop_controller->backdrop_window());
+  EXPECT_TRUE(wm::GetWindowState(window2.get())->IsMaximized());
+
+  // Tests that the second window despite being larger than the first window
+  // does not animate as it is hidden behind the backdrop. On exit, it still
+  // animates as the backdrop is not visible yet.
+  std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
+                                           gfx::RectF(100.f, 100.f)};
+  CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
+                       {true, false}, {true, true});
+}
+
 }  // namespace ash
diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc
index fa72cf7..64dc96c 100644
--- a/ash/wm/overview/overview_item.cc
+++ b/ash/wm/overview/overview_item.cc
@@ -840,10 +840,8 @@
 }
 
 void OverviewItem::HandleLongPressEvent(const gfx::PointF& location_in_screen) {
-  if (!ShouldAllowSplitView())
-    return;
-
-  overview_session_->StartSplitViewDragMode(location_in_screen);
+  if (ShouldAllowSplitView() || features::IsVirtualDesksEnabled())
+    overview_session_->StartNormalDragMode(location_in_screen);
 }
 
 void OverviewItem::HandleFlingStartEvent(const gfx::PointF& location_in_screen,
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index 1ceaf3a..7d2473e 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -505,10 +505,10 @@
       restore_focus_window_ = nullptr;
   }
 
-  MaybeCreateAndPositionNoWindowsWidget();
-
   GetGridWithOverviewItem(overview_item)->RemoveItem(overview_item);
   --num_items_;
+
+  MaybeCreateAndPositionNoWindowsWidget();
 }
 
 void OverviewSession::AddDropTargetForDraggingFromOverview(
@@ -525,8 +525,8 @@
 void OverviewSession::InitiateDrag(OverviewItem* item,
                                    const gfx::PointF& location_in_screen) {
   window_drag_controller_ =
-      std::make_unique<OverviewWindowDragController>(this);
-  window_drag_controller_->InitiateDrag(item, location_in_screen);
+      std::make_unique<OverviewWindowDragController>(this, item);
+  window_drag_controller_->InitiateDrag(location_in_screen);
 
   for (std::unique_ptr<OverviewGrid>& grid : grid_list_)
     grid->OnSelectorItemDragStarted(item);
@@ -550,9 +550,9 @@
     grid->OnSelectorItemDragEnded(snap);
 }
 
-void OverviewSession::StartSplitViewDragMode(
+void OverviewSession::StartNormalDragMode(
     const gfx::PointF& location_in_screen) {
-  window_drag_controller_->StartSplitViewDragMode(location_in_screen);
+  window_drag_controller_->StartNormalDragMode(location_in_screen);
 }
 
 void OverviewSession::Fling(OverviewItem* item,
@@ -706,8 +706,8 @@
       DesksController::Get()->AreDesksBeingModified()) {
     // Activating a desk from its mini view will activate its most-recently used
     // window, but this should not result in ending overview mode now.
-    // DesksBarView will end it explicitly. This will become significant when
-    // the desk activation animation is added.
+    // Overview will be ended explicitly as part of the desk activation
+    // animation.
     return;
   }
 
diff --git a/ash/wm/overview/overview_session.h b/ash/wm/overview/overview_session.h
index e98f8d6c2..48ea8b5 100644
--- a/ash/wm/overview/overview_session.h
+++ b/ash/wm/overview/overview_session.h
@@ -173,7 +173,7 @@
   void InitiateDrag(OverviewItem* item, const gfx::PointF& location_in_screen);
   void Drag(OverviewItem* item, const gfx::PointF& location_in_screen);
   void CompleteDrag(OverviewItem* item, const gfx::PointF& location_in_screen);
-  void StartSplitViewDragMode(const gfx::PointF& location_in_screen);
+  void StartNormalDragMode(const gfx::PointF& location_in_screen);
   void Fling(OverviewItem* item,
              const gfx::PointF& location_in_screen,
              float velocity_x,
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index 118eab5..cb28d73 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -3110,7 +3110,7 @@
     }
     overview_session()->InitiateDrag(item, start_location);
     if (long_press)
-      overview_session()->StartSplitViewDragMode(start_location);
+      overview_session()->StartNormalDragMode(start_location);
     overview_session()->Drag(item, end_location);
     overview_session()->CompleteDrag(item, end_location);
   }
@@ -3214,7 +3214,7 @@
       overview_session()->window_drag_controller();
   EXPECT_EQ(DragBehavior::kUndefined, drag_controller->current_drag_behavior());
   generator->MoveTouchBy(20, 0);
-  EXPECT_EQ(DragBehavior::kDragToSnap,
+  EXPECT_EQ(DragBehavior::kNormalDrag,
             drag_controller->current_drag_behavior());
   generator->ReleaseTouch();
   EXPECT_EQ(DragBehavior::kNoDrag, drag_controller->current_drag_behavior());
diff --git a/ash/wm/overview/overview_window_drag_controller.cc b/ash/wm/overview/overview_window_drag_controller.cc
index 505a923..bd5ae68 100644
--- a/ash/wm/overview/overview_window_drag_controller.cc
+++ b/ash/wm/overview/overview_window_drag_controller.cc
@@ -6,9 +6,11 @@
 
 #include <memory>
 
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/presentation_time_recorder.h"
 #include "ash/screen_util.h"
 #include "ash/shell.h"
+#include "ash/wm/desks/desk_preview_view.h"
 #include "ash/wm/overview/overview_constants.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_grid.h"
@@ -48,6 +50,10 @@
 constexpr float kFlingToCloseVelocityThreshold = 2000.f;
 constexpr float kItemMinOpacity = 0.4f;
 
+// The opacity the dragged item uses once its dragged location intersects with
+// the DesksBarView.
+constexpr float kDragToDeskItemOpacity = 0.6f;
+
 // The UMA histogram that records presentation time for window dragging
 // operation in overview mode.
 constexpr char kOverviewWindowDragHistogram[] =
@@ -60,22 +66,44 @@
       kOcclusionPauseDurationForDragMs);
 }
 
+// Returns the scaled-down size of the dragged item that should be used when
+// it's dragged over the DesksBarView.
+gfx::SizeF GetItemSizeWhenOnDesksBar(OverviewItem* item) {
+  DCHECK(item);
+
+  // Scale the original window's size down such that it fits within the bounds
+  // of the DeskPreviewView.
+  const aura::Window* window = item->GetWindow();
+  DCHECK(window);
+  const float root_height = window->GetRootWindow()->bounds().height();
+  const float scale_factor = DeskPreviewView::GetHeight() / float{root_height};
+  const gfx::SizeF window_size(window->bounds().size());
+  gfx::SizeF scaled_size = gfx::ScaleSize(window_size, scale_factor);
+  // Add the margins overview mode adds around the window's contents.
+  scaled_size.Enlarge(2 * kWindowMargin, 2 * kWindowMargin + kHeaderHeightDp);
+  return scaled_size;
+}
+
 }  // namespace
 
 OverviewWindowDragController::OverviewWindowDragController(
-    OverviewSession* overview_session)
+    OverviewSession* overview_session,
+    OverviewItem* item)
     : overview_session_(overview_session),
-      split_view_controller_(Shell::Get()->split_view_controller()) {}
+      split_view_controller_(Shell::Get()->split_view_controller()),
+      item_(item),
+      on_desks_bar_item_size_(GetItemSizeWhenOnDesksBar(item)),
+      should_allow_split_view_(ShouldAllowSplitView()),
+      virtual_desks_enabled_(features::IsVirtualDesksEnabled()) {}
 
 OverviewWindowDragController::~OverviewWindowDragController() = default;
 
 void OverviewWindowDragController::InitiateDrag(
-    OverviewItem* item,
     const gfx::PointF& location_in_screen) {
-  item_ = item;
   initial_event_location_ = location_in_screen;
   initial_centerpoint_ = item_->target_bounds().CenterPoint();
-  if (ShouldAllowSplitView()) {
+  original_opacity_ = item_->GetOpacity();
+  if (should_allow_split_view_) {
     started_in_snap_region_ =
         GetSnapPosition(location_in_screen) != SplitViewController::NONE;
   }
@@ -100,110 +128,48 @@
 
     if (std::abs(distance.x()) < std::abs(distance.y())) {
       current_drag_behavior_ = DragBehavior::kDragToClose;
-      original_opacity_ = item_->GetOpacity();
       overview_session_->GetGridWithRootWindow(item_->root_window())
           ->StartNudge(item_);
       did_move_ = true;
-    } else if (ShouldAllowSplitView()) {
-      StartSplitViewDragMode(location_in_screen);
+    } else {
+      StartNormalDragMode(location_in_screen);
     }
   }
 
-  // Update the state based on the drag behavior.
-  if (current_drag_behavior_ == DragBehavior::kDragToClose) {
-    // Update |item_|'s opacity based on its distance. |item_|'s x coordinate
-    // should not change while in drag to close state.
-    float val = std::abs(location_in_screen.y() - initial_event_location_.y()) /
-                kDragToCloseDistanceThresholdDp;
-    overview_session_->GetGridWithRootWindow(item_->root_window())
-        ->UpdateNudge(item_, val);
-    val = base::ClampToRange(val, 0.f, 1.f);
-    float opacity = original_opacity_;
-    if (opacity > kItemMinOpacity)
-      opacity = original_opacity_ - val * (original_opacity_ - kItemMinOpacity);
-    item_->SetOpacity(opacity);
-  } else if (current_drag_behavior_ == DragBehavior::kDragToSnap) {
-    UpdateDragIndicatorsAndOverviewGrid(location_in_screen);
-  }
+  gfx::RectF bounds(item_->target_bounds());
+  if (current_drag_behavior_ == DragBehavior::kDragToClose)
+    bounds = ContinueDragToClose(location_in_screen);
+  else if (current_drag_behavior_ == DragBehavior::kNormalDrag)
+    bounds = ContinueNormalDrag(location_in_screen);
 
   if (presentation_time_recorder_)
     presentation_time_recorder_->RequestNext();
 
-  // Update the dragged |item_|'s bounds accordingly. The distance from the new
-  // location to the new centerpoint should be the same it was initially. Do not
-  // update x bounds if dragging to close.
-  gfx::RectF bounds(item_->target_bounds());
-  const gfx::PointF centerpoint =
-      location_in_screen - (initial_event_location_ - initial_centerpoint_);
-  if (current_drag_behavior_ == DragBehavior::kDragToSnap)
-    bounds.set_x(centerpoint.x() - bounds.width() / 2.f);
-  bounds.set_y(centerpoint.y() - bounds.height() / 2.f);
   item_->SetBounds(bounds, OVERVIEW_ANIMATION_NONE);
 }
 
 OverviewWindowDragController::DragResult
 OverviewWindowDragController::CompleteDrag(
     const gfx::PointF& location_in_screen) {
-  // Update the split view divider bar stuatus if necessary. The divider bar
-  // should be placed above the dragged window after drag ends. Note here the
-  // passed paramters |snap_position_| and |location_in_screen| won't be used in
-  // this function for this case, but they are passed in as placeholders.
-  if (ShouldAllowSplitView()) {
-    split_view_controller_->OnWindowDragEnded(
-        item_->GetWindow(), snap_position_,
-        gfx::ToRoundedPoint(location_in_screen));
-  }
-
-  // Update window grid bounds and |snap_position_| in case the screen
-  // orientation was changed.
-  if (current_drag_behavior_ == DragBehavior::kDragToSnap) {
-    UpdateDragIndicatorsAndOverviewGrid(location_in_screen);
-    overview_session_->SetSplitViewDragIndicatorsIndicatorState(
-        IndicatorState::kNone, gfx::Point());
-  }
-
-  DragResult result;
+  DragResult result = DragResult::kNeverDisambiguated;
   switch (current_drag_behavior_) {
     case DragBehavior::kNoDrag:
       NOTREACHED();
-      result = DragResult::kNeverDisambiguated;
       break;
+
     case DragBehavior::kUndefined:
       ActivateDraggedWindow();
-      result = DragResult::kNeverDisambiguated;
       break;
-    case DragBehavior::kDragToSnap:
-      overview_session_->RemoveDropTargetForDraggingFromOverview(item_);
-      // If the window was dragged around but should not be snapped, move it
-      // back to overview window grid.
-      if (!ShouldUpdateDragIndicatorsOrSnap(location_in_screen) ||
-          snap_position_ == SplitViewController::NONE) {
-        item_->set_should_restack_on_animation_end(true);
-        overview_session_->PositionWindows(/*animate=*/true);
-        result = DragResult::kCanceledDragToSnap;
-      } else {
-        SnapWindow(snap_position_);
-        result = DragResult::kSuccessfulDragToSnap;
-      }
+
+    case DragBehavior::kNormalDrag:
+      result = CompleteNormalDrag(location_in_screen);
       break;
+
     case DragBehavior::kDragToClose:
-      // If we are in drag to close mode close the window if it has been dragged
-      // enough, otherwise reposition it and set its opacity back to its
-      // original value.
-      overview_session_->GetGridWithRootWindow(item_->root_window())
-          ->EndNudge();
-      if (std::abs((location_in_screen - initial_event_location_).y()) >
-          kDragToCloseDistanceThresholdDp) {
-        item_->AnimateAndCloseWindow(
-            (location_in_screen - initial_event_location_).y() < 0);
-        result = DragResult::kSuccessfulDragToClose;
-      } else {
-        item_->SetOpacity(original_opacity_);
-        overview_session_->PositionWindows(/*animate=*/true);
-        result = DragResult::kCanceledDragToClose;
-      }
+      result = CompleteDragToClose(location_in_screen);
       break;
   }
+
   did_move_ = false;
   item_ = nullptr;
   current_drag_behavior_ = DragBehavior::kNoDrag;
@@ -212,26 +178,26 @@
   return result;
 }
 
-void OverviewWindowDragController::StartSplitViewDragMode(
+void OverviewWindowDragController::StartNormalDragMode(
     const gfx::PointF& location_in_screen) {
-  DCHECK(ShouldAllowSplitView());
-
-  overview_session_->AddDropTargetForDraggingFromOverview(item_);
+  did_move_ = true;
+  current_drag_behavior_ = DragBehavior::kNormalDrag;
   item_->ScaleUpSelectedItem(
       OVERVIEW_ANIMATION_LAYOUT_OVERVIEW_ITEMS_IN_OVERVIEW);
+  original_scaled_size_ = item_->target_bounds().size();
 
-  did_move_ = true;
-  current_drag_behavior_ = DragBehavior::kDragToSnap;
-  overview_session_->SetSplitViewDragIndicatorsIndicatorState(
-      CanSnapInSplitview(item_->GetWindow()) ? IndicatorState::kDragArea
-                                             : IndicatorState::kCannotSnap,
-      gfx::ToRoundedPoint(location_in_screen));
+  if (should_allow_split_view_) {
+    overview_session_->AddDropTargetForDraggingFromOverview(item_);
+    overview_session_->SetSplitViewDragIndicatorsIndicatorState(
+        CanSnapInSplitview(item_->GetWindow()) ? IndicatorState::kDragArea
+                                               : IndicatorState::kCannotSnap,
+        gfx::ToRoundedPoint(location_in_screen));
 
-  // Update the split view divider bar status if necessary. If splitview is
-  // active when dragging the overview window, the split divider bar should be
-  // placed below the dragged window during dragging.
-  if (ShouldAllowSplitView())
+    // Update the split view divider bar status if necessary. If splitview is
+    // active when dragging the overview window, the split divider bar should be
+    // placed below the dragged window during dragging.
     split_view_controller_->OnWindowDragStarted(item_->GetWindow());
+  }
 }
 
 OverviewWindowDragController::DragResult OverviewWindowDragController::Fling(
@@ -241,12 +207,6 @@
   if (current_drag_behavior_ == DragBehavior::kDragToClose ||
       current_drag_behavior_ == DragBehavior::kUndefined) {
     if (std::abs(velocity_y) > kFlingToCloseVelocityThreshold) {
-      if (ShouldAllowSplitView()) {
-        split_view_controller_->OnWindowDragEnded(
-            item_->GetWindow(), snap_position_,
-            gfx::ToRoundedPoint(location_in_screen));
-      }
-
       item_->AnimateAndCloseWindow(
           (location_in_screen - initial_event_location_).y() < 0);
       did_move_ = false;
@@ -270,7 +230,7 @@
   // and the selected window cannot be snapped, exit splitview and activate
   // the selected window, and also exit the overview.
   SplitViewState split_state = split_view_controller_->state();
-  if (!ShouldAllowSplitView() || split_state == SplitViewState::kNoSnap) {
+  if (!should_allow_split_view_ || split_state == SplitViewState::kNoSnap) {
     overview_session_->SelectWindow(item_);
   } else if (CanSnapInSplitview(item_->GetWindow())) {
     SnapWindow(split_state == SplitViewState::kLeftSnapped
@@ -286,7 +246,7 @@
 }
 
 void OverviewWindowDragController::ResetGesture() {
-  if (current_drag_behavior_ == DragBehavior::kDragToSnap) {
+  if (current_drag_behavior_ == DragBehavior::kNormalDrag) {
     overview_session_->RemoveDropTargetForDraggingFromOverview(item_);
     overview_session_->SetSplitViewDragIndicatorsIndicatorState(
         IndicatorState::kNone, gfx::Point());
@@ -303,9 +263,154 @@
   overview_session_ = nullptr;
 }
 
+gfx::RectF OverviewWindowDragController::ContinueDragToClose(
+    const gfx::PointF& location_in_screen) {
+  DCHECK_EQ(current_drag_behavior_, DragBehavior::kDragToClose);
+
+  // Update the dragged |item_|'s bounds accordingly. The distance from the new
+  // location to the new centerpoint should be the same it was initially.
+  gfx::RectF bounds(item_->target_bounds());
+  const gfx::PointF centerpoint =
+      location_in_screen - (initial_event_location_ - initial_centerpoint_);
+
+  // Update |item_|'s opacity based on its distance. |item_|'s x coordinate
+  // should not change while in drag to close state.
+  float val = std::abs(location_in_screen.y() - initial_event_location_.y()) /
+              kDragToCloseDistanceThresholdDp;
+  overview_session_->GetGridWithRootWindow(item_->root_window())
+      ->UpdateNudge(item_, val);
+  val = base::ClampToRange(val, 0.f, 1.f);
+  float opacity = original_opacity_;
+  if (opacity > kItemMinOpacity)
+    opacity = original_opacity_ - val * (original_opacity_ - kItemMinOpacity);
+  item_->SetOpacity(opacity);
+
+  // When dragging to close, only update the y component.
+  bounds.set_y(centerpoint.y() - bounds.height() / 2.f);
+  return bounds;
+}
+
+OverviewWindowDragController::DragResult
+OverviewWindowDragController::CompleteDragToClose(
+    const gfx::PointF& location_in_screen) {
+  DCHECK_EQ(current_drag_behavior_, DragBehavior::kDragToClose);
+
+  // Close the window if it has been dragged enough, otherwise reposition it and
+  // set its opacity back to its original value.
+  overview_session_->GetGridWithRootWindow(item_->root_window())->EndNudge();
+  const float y_distance = (location_in_screen - initial_event_location_).y();
+  if (std::abs(y_distance) > kDragToCloseDistanceThresholdDp) {
+    item_->AnimateAndCloseWindow(/*up=*/y_distance < 0);
+    return DragResult::kSuccessfulDragToClose;
+  }
+
+  item_->SetOpacity(original_opacity_);
+  overview_session_->PositionWindows(/*animate=*/true);
+  return DragResult::kCanceledDragToClose;
+}
+
+gfx::RectF OverviewWindowDragController::ContinueNormalDrag(
+    const gfx::PointF& location_in_screen) {
+  DCHECK_EQ(current_drag_behavior_, DragBehavior::kNormalDrag);
+
+  // Update the dragged |item_|'s bounds accordingly. The distance from the new
+  // location to the new centerpoint should be the same it was initially unless
+  // the item is over the DeskBarView, in which case we scale it down and center
+  // it around the drag location.
+  gfx::RectF bounds(item_->target_bounds());
+  gfx::PointF centerpoint =
+      location_in_screen - (initial_event_location_ - initial_centerpoint_);
+
+  if (virtual_desks_enabled_) {
+    if (item_->overview_grid()->UpdateDesksBarDragDetails(
+            gfx::ToRoundedPoint(location_in_screen))) {
+      // The drag location intersects the bounds of the DesksBarView, in this
+      // case we scale down the item, and center it around the drag location.
+      bounds.set_size(on_desks_bar_item_size_);
+      item_->SetOpacity(kDragToDeskItemOpacity);
+      centerpoint = location_in_screen;
+      // To make the dragged window contents appear centered around the drag
+      // location, we need to take into account the margins applied on the
+      // target bounds, and offset up the centerpoint by half that amount, so
+      // that the transformed bounds of the window contents move up to be
+      // centered around the cursor.
+      centerpoint.Offset(0, (-kWindowMargin - kHeaderHeightDp) / 2);
+    } else {
+      bounds.set_size(original_scaled_size_);
+      item_->SetOpacity(original_opacity_);
+    }
+  }
+
+  if (should_allow_split_view_)
+    UpdateDragIndicatorsAndOverviewGrid(location_in_screen);
+
+  bounds.set_x(centerpoint.x() - bounds.width() / 2.f);
+  bounds.set_y(centerpoint.y() - bounds.height() / 2.f);
+  return bounds;
+}
+
+OverviewWindowDragController::DragResult
+OverviewWindowDragController::CompleteNormalDrag(
+    const gfx::PointF& location_in_screen) {
+  DCHECK_EQ(current_drag_behavior_, DragBehavior::kNormalDrag);
+
+  // Remove the drop target if any (which may exist when SplitView is
+  // enabled either in tablet or clamshell modes).
+  if (item_->overview_grid()->drop_target_widget())
+    item_->overview_grid()->RemoveDropTarget();
+
+  // Attempt to move a window to a different desk.
+  const gfx::Point rounded_screen_point =
+      gfx::ToRoundedPoint(location_in_screen);
+  if (virtual_desks_enabled_) {
+    item_->SetOpacity(original_opacity_);
+
+    if (item_->overview_grid()->MaybeDropItemOnDeskMiniView(
+            rounded_screen_point, item_)) {
+      // Window was successfully moved to another desk, and |item_| was
+      // removed from the grid. It may never be accessed after this.
+      item_ = nullptr;
+      overview_session_->PositionWindows(/*animate=*/true);
+      return DragResult::kSuccessfulDragToDesk;
+    }
+  }
+
+  // Attempt to snap a window if SplitView is enabled.
+  DCHECK(item_);
+  if (should_allow_split_view_) {
+    // Update the split view divider bar stuatus if necessary. The divider bar
+    // should be placed above the dragged window after drag ends. Note here the
+    // passed parameters |snap_position_| and |location_in_screen| won't be used
+    // in this function for this case, but they are passed in as placeholders.
+    split_view_controller_->OnWindowDragEnded(
+        item_->GetWindow(), snap_position_, rounded_screen_point);
+
+    // Update window grid bounds and |snap_position_| in case the screen
+    // orientation was changed.
+    UpdateDragIndicatorsAndOverviewGrid(location_in_screen);
+    overview_session_->SetSplitViewDragIndicatorsIndicatorState(
+        IndicatorState::kNone, gfx::Point());
+
+    // If the window was dragged around but should not be snapped, move it
+    // back to overview window grid.
+    if (!ShouldUpdateDragIndicatorsOrSnap(location_in_screen) ||
+        snap_position_ == SplitViewController::NONE) {
+      item_->set_should_restack_on_animation_end(true);
+      overview_session_->PositionWindows(/*animate=*/true);
+      return DragResult::kCanceledDragToSnap;
+    }
+
+    SnapWindow(snap_position_);
+    return DragResult::kSuccessfulDragToSnap;
+  }
+
+  overview_session_->PositionWindows(/*animate=*/true);
+  return DragResult::kNeverDisambiguated;
+}
+
 void OverviewWindowDragController::UpdateDragIndicatorsAndOverviewGrid(
     const gfx::PointF& location_in_screen) {
-  DCHECK(ShouldAllowSplitView());
+  DCHECK(should_allow_split_view_);
   if (!ShouldUpdateDragIndicatorsOrSnap(location_in_screen))
     return;
 
@@ -394,7 +499,7 @@
 SplitViewController::SnapPosition OverviewWindowDragController::GetSnapPosition(
     const gfx::PointF& location_in_screen) const {
   DCHECK(item_);
-  DCHECK(ShouldAllowSplitView());
+  DCHECK(should_allow_split_view_);
   gfx::Rect area(
       screen_util::GetDisplayWorkAreaBoundsInParent(item_->GetWindow()));
   ::wm::ConvertRectToScreen(item_->GetWindow()->GetRootWindow(), &area);
diff --git a/ash/wm/overview/overview_window_drag_controller.h b/ash/wm/overview/overview_window_drag_controller.h
index e3f8d5d..f2f3368 100644
--- a/ash/wm/overview/overview_window_drag_controller.h
+++ b/ash/wm/overview/overview_window_drag_controller.h
@@ -26,33 +26,49 @@
 class ASH_EXPORT OverviewWindowDragController {
  public:
   enum class DragBehavior {
-    kNoDrag,       // No drag has started.
-    kUndefined,    // Drag has started, but it is undecided whether we want to
-                   // drag to snap or drag to close yet.
-    kDragToSnap,   // On drag complete, the window will be snapped, if it meets
-                   // requirements.
-    kDragToClose,  // On drag complete, the window will be closed, if it meets
-                   // requirements.
+    // No drag has started.
+    kNoDrag,
+    // Drag has started, but it is undecided whether we want to drag to snap or
+    // drag to close yet.
+    kUndefined,
+    // On drag complete, the window will be snapped, if it meets requirements,
+    // or moved to another desk if dropped on one of the desks' mini_views. This
+    // mode is triggered if the the window is initially dragged horizontally
+    // more than vertically (more in X than Y), or if the window item in the
+    // overview grid was gesture long pressed.
+    kNormalDrag,
+    // On drag complete, the window will be closed, if it meets requirements.
+    // This mode is triggered when the window is initially dragged vertically
+    // more than horizontally (more in Y than in X).
+    kDragToClose,
   };
 
   enum class DragResult {
-    kNeverDisambiguated,     // The drag ended without ever being disambiguated
-                             // between drag-to-snap and drag-to-close.
-    kSuccessfulDragToSnap,   // The drag resulted in snapping the window.
-    kCanceledDragToSnap,     // The drag was considered as drag-to-snap, but did
-                             // not result in snapping the window.
-    kSuccessfulDragToClose,  // The drag resulted in closing the window.
-    kCanceledDragToClose,    // The drag was considered as drag-to-close, but
-                             // did not result in closing the window.
+    // The drag ended without ever being disambiguated between drag-to-snap and
+    // drag-to-close.
+    kNeverDisambiguated,
+    // The drag resulted in snapping the window.
+    kSuccessfulDragToSnap,
+    // The drag was considered as drag-to-snap, but did not result in snapping
+    // the window.
+    kCanceledDragToSnap,
+    // The drag resulted in closing the window.
+    kSuccessfulDragToClose,
+    // The drag was considered as drag-to-close, but did not result in closing
+    // the window.
+    kCanceledDragToClose,
+    // The drag resulted in moving the window to another desk.
+    kSuccessfulDragToDesk,
   };
 
-  explicit OverviewWindowDragController(OverviewSession* overview_session);
+  OverviewWindowDragController(OverviewSession* overview_session,
+                               OverviewItem* item);
   ~OverviewWindowDragController();
 
-  void InitiateDrag(OverviewItem* item, const gfx::PointF& location_in_screen);
+  void InitiateDrag(const gfx::PointF& location_in_screen);
   void Drag(const gfx::PointF& location_in_screen);
   DragResult CompleteDrag(const gfx::PointF& location_in_screen);
-  void StartSplitViewDragMode(const gfx::PointF& location_in_screen);
+  void StartNormalDragMode(const gfx::PointF& location_in_screen);
   DragResult Fling(const gfx::PointF& location_in_screen,
                    float velocity_x,
                    float velocity_y);
@@ -70,6 +86,16 @@
   DragBehavior current_drag_behavior() { return current_drag_behavior_; }
 
  private:
+  // Methods to continue and complete the drag when the drag mode is
+  // kDragToClose.
+  gfx::RectF ContinueDragToClose(const gfx::PointF& location_in_screen);
+  DragResult CompleteDragToClose(const gfx::PointF& location_in_screen);
+
+  // Methods to continue and complete the drag when the drag mode is
+  // kNormalDrag.
+  gfx::RectF ContinueNormalDrag(const gfx::PointF& location_in_screen);
+  DragResult CompleteNormalDrag(const gfx::PointF& location_in_screen);
+
   // Updates visuals for the user while dragging items around.
   void UpdateDragIndicatorsAndOverviewGrid(
       const gfx::PointF& location_in_screen);
@@ -103,6 +129,22 @@
   // new bounds on a drag event.
   gfx::PointF initial_centerpoint_;
 
+  // The scaled-down size of the dragged item once the drag location is on the
+  // DesksBarView. We size the item down so that it fits inside the desks'
+  // preview view.
+  const gfx::SizeF on_desks_bar_item_size_;
+
+  // The original size of the dragged item after we scale it up when we start
+  // dragging it. The item is restored to this size once it no longer intersects
+  // with the DesksBarView.
+  gfx::SizeF original_scaled_size_;
+
+  // True if SplitView is enabled.
+  const bool should_allow_split_view_;
+
+  // True if the Virtual Desks feature is enabled.
+  const bool virtual_desks_enabled_;
+
   // False if the initial drag location was not a snap region, or if it was in
   // a snap region but the drag has since moved out.
   bool started_in_snap_region_ = false;
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index 5d887f7..eb8e2124 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -9,6 +9,7 @@
 
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/display/screen_orientation_controller.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/presentation_time_recorder.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/screen_util.h"
@@ -17,6 +18,7 @@
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/toast/toast_data.h"
 #include "ash/system/toast/toast_manager.h"
+#include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_grid.h"
@@ -903,6 +905,15 @@
 void SplitViewController::OnWindowActivated(ActivationReason reason,
                                             aura::Window* gained_active,
                                             aura::Window* lost_active) {
+  if (features::IsVirtualDesksEnabled() &&
+      DesksController::Get()->AreDesksBeingModified()) {
+    // Activating a desk from its mini view will activate its most-recently used
+    // window, but this should not result in snapping and ending overview mode
+    // now. Overview will be ended explicitly as part of the desk activation
+    // animation.
+    return;
+  }
+
   // This may be called while SnapWindow is still underway because SnapWindow
   // will end the overview start animations which will cause the overview focus
   // window to be activated.
diff --git a/ash/wm/splitview/split_view_controller_unittest.cc b/ash/wm/splitview/split_view_controller_unittest.cc
index da4a486..d9c4235 100644
--- a/ash/wm/splitview/split_view_controller_unittest.cc
+++ b/ash/wm/splitview/split_view_controller_unittest.cc
@@ -3207,8 +3207,7 @@
   OverviewGrid* current_grid =
       overview_session->GetGridWithRootWindow(window1->GetRootWindow());
   ASSERT_TRUE(current_grid);
-  views::Widget* drop_target_widget =
-      current_grid->drop_target_widget_for_testing();
+  views::Widget* drop_target_widget = current_grid->drop_target_widget();
   EXPECT_TRUE(drop_target_widget);
 
   OverviewItem* drop_target = current_grid->GetOverviewItemContaining(
@@ -3234,7 +3233,7 @@
   EXPECT_TRUE(overview_session->IsWindowInOverview(window1.get()));
   EXPECT_TRUE(overview_session->IsWindowInOverview(window3.get()));
   // Test that the new window item widget has been destroyed.
-  EXPECT_FALSE(current_grid->drop_target_widget_for_testing());
+  EXPECT_FALSE(current_grid->drop_target_widget());
 }
 
 // Tests that if overview is ended because of releasing the dragged window, we
@@ -3360,8 +3359,7 @@
           window1.get());
   EXPECT_EQ(current_grid->bounds(), work_area_bounds);
   // The drop target should be visible.
-  views::Widget* drop_target_widget =
-      current_grid->drop_target_widget_for_testing();
+  views::Widget* drop_target_widget = current_grid->drop_target_widget();
   EXPECT_TRUE(drop_target_widget);
   // Drop target's bounds has been set when added it into overview, which is not
   // equals to the window's bounds.
@@ -3409,7 +3407,7 @@
   current_grid = selector_controller->overview_session()->GetGridWithRootWindow(
       window1->GetRootWindow());
   // The drop target should be visible.
-  drop_target_widget = current_grid->drop_target_widget_for_testing();
+  drop_target_widget = current_grid->drop_target_widget();
   EXPECT_TRUE(drop_target_widget);
   EXPECT_TRUE(drop_target_widget->IsVisible());
   EXPECT_EQ(drop_target_widget->GetNativeWindow()->bounds(),
@@ -3453,7 +3451,7 @@
   // window's size.
   current_grid = selector_controller->overview_session()->GetGridWithRootWindow(
       window1->GetRootWindow());
-  drop_target_widget = current_grid->drop_target_widget_for_testing();
+  drop_target_widget = current_grid->drop_target_widget();
   EXPECT_TRUE(drop_target_widget);
   EXPECT_TRUE(drop_target_widget->IsVisible());
   EXPECT_EQ(drop_target_widget->GetNativeWindow()->bounds(),
diff --git a/ash/wm/splitview/split_view_drag_indicators_unittest.cc b/ash/wm/splitview/split_view_drag_indicators_unittest.cc
index 92dcaae..9a67239 100644
--- a/ash/wm/splitview/split_view_drag_indicators_unittest.cc
+++ b/ash/wm/splitview/split_view_drag_indicators_unittest.cc
@@ -289,7 +289,7 @@
   gfx::PointF start_location(item->target_bounds().CenterPoint());
   overview_session_->InitiateDrag(item, start_location);
   EXPECT_EQ(IndicatorState::kNone, indicator_state());
-  overview_session_->StartSplitViewDragMode(start_location);
+  overview_session_->StartNormalDragMode(start_location);
   EXPECT_EQ(IndicatorState::kDragArea, indicator_state());
 
   // Reset the gesture so we stay in overview mode.
@@ -332,7 +332,7 @@
   OverviewItem* item = GetOverviewItemForWindow(unsnappable_window.get());
   gfx::PointF start_location(item->target_bounds().CenterPoint());
   overview_session_->InitiateDrag(item, start_location);
-  overview_session_->StartSplitViewDragMode(start_location);
+  overview_session_->StartNormalDragMode(start_location);
   EXPECT_EQ(IndicatorState::kCannotSnap, indicator_state());
   const gfx::PointF end_location1(0.f, 0.f);
   overview_session_->Drag(item, end_location1);
diff --git a/ash/wm/workspace/backdrop_controller.cc b/ash/wm/workspace/backdrop_controller.cc
index 0932224..3414669 100644
--- a/ash/wm/workspace/backdrop_controller.cc
+++ b/ash/wm/workspace/backdrop_controller.cc
@@ -163,6 +163,40 @@
   container_->StackChildAbove(window, backdrop_window_);
 }
 
+aura::Window* BackdropController::GetTopmostWindowWithBackdrop() {
+  const aura::Window::Windows windows = container_->children();
+  for (auto window_iter = windows.rbegin(); window_iter != windows.rend();
+       ++window_iter) {
+    aura::Window* window = *window_iter;
+    if (window == backdrop_window_)
+      continue;
+
+    if (window->type() != aura::client::WINDOW_TYPE_NORMAL)
+      continue;
+
+    auto* window_state = wm::GetWindowState(window);
+    if (window_state->IsMinimized())
+      continue;
+
+    // No need to check the visibility or the activateability of the window if
+    // this is an inactive desk's container.
+    if (!desks_util::IsDeskContainer(container_) ||
+        desks_util::IsActiveDeskContainer(container_)) {
+      if (!window->layer()->GetTargetVisibility())
+        continue;
+
+      if (!::wm::CanActivateWindow(window))
+        continue;
+    }
+
+    if (!WindowShouldHaveBackdrop(window))
+      continue;
+
+    return window;
+  }
+  return nullptr;
+}
+
 void BackdropController::OnSplitViewModeStarting() {
   Shell::Get()->split_view_controller()->AddObserver(this);
 }
@@ -251,40 +285,6 @@
   }
 }
 
-aura::Window* BackdropController::GetTopmostWindowWithBackdrop() {
-  const aura::Window::Windows windows = container_->children();
-  for (auto window_iter = windows.rbegin(); window_iter != windows.rend();
-       ++window_iter) {
-    aura::Window* window = *window_iter;
-    if (window == backdrop_window_)
-      continue;
-
-    if (window->type() != aura::client::WINDOW_TYPE_NORMAL)
-      continue;
-
-    auto* window_state = wm::GetWindowState(window);
-    if (window_state->IsMinimized())
-      continue;
-
-    // No need to check the visibility or the activateability of the window if
-    // this is an inactive desk's container.
-    if (!desks_util::IsDeskContainer(container_) ||
-        desks_util::IsActiveDeskContainer(container_)) {
-      if (!window->layer()->GetTargetVisibility())
-        continue;
-
-      if (!::wm::CanActivateWindow(window))
-        continue;
-    }
-
-    if (!WindowShouldHaveBackdrop(window))
-      continue;
-
-    return window;
-  }
-  return nullptr;
-}
-
 bool BackdropController::WindowShouldHaveBackdrop(aura::Window* window) {
   if (window->GetAllPropertyKeys().count(kBackdropWindowMode)) {
     BackdropWindowMode backdrop_mode = window->GetProperty(kBackdropWindowMode);
diff --git a/ash/wm/workspace/backdrop_controller.h b/ash/wm/workspace/backdrop_controller.h
index 276691e..6cc60d1 100644
--- a/ash/wm/workspace/backdrop_controller.h
+++ b/ash/wm/workspace/backdrop_controller.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "ash/accessibility/accessibility_observer.h"
+#include "ash/ash_export.h"
 #include "ash/public/cpp/split_view.h"
 #include "ash/shell_observer.h"
 #include "ash/wallpaper/wallpaper_controller_observer.h"
@@ -38,11 +39,11 @@
 // 1) Has a aura::client::kHasBackdrop property = true.
 // 2) BackdropDelegate::HasBackdrop(aura::Window* window) returns true.
 // 3) Active ARC window when the spoken feedback is enabled.
-class BackdropController : public AccessibilityObserver,
-                           public ShellObserver,
-                           public OverviewObserver,
-                           public SplitViewObserver,
-                           public WallpaperControllerObserver {
+class ASH_EXPORT BackdropController : public AccessibilityObserver,
+                                      public ShellObserver,
+                                      public OverviewObserver,
+                                      public SplitViewObserver,
+                                      public WallpaperControllerObserver {
  public:
   explicit BackdropController(aura::Window* container);
   ~BackdropController() override;
@@ -60,6 +61,9 @@
   // the other windows in the container.
   void UpdateBackdrop();
 
+  // Returns the current visible top level window in the container.
+  aura::Window* GetTopmostWindowWithBackdrop();
+
   aura::Window* backdrop_window() { return backdrop_window_; }
 
   // ShellObserver:
@@ -91,9 +95,6 @@
 
   void Layout();
 
-  // Returns the current visible top level window in the container.
-  aura::Window* GetTopmostWindowWithBackdrop();
-
   bool WindowShouldHaveBackdrop(aura::Window* window);
 
   // Show the backdrop window.
diff --git a/base/BUILD.gn b/base/BUILD.gn
index e0810ff..b451a594 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -487,6 +487,8 @@
     "message_loop/work_id_provider.h",
     "metrics/bucket_ranges.cc",
     "metrics/bucket_ranges.h",
+    "metrics/crc32.cc",
+    "metrics/crc32.h",
     "metrics/dummy_histogram.cc",
     "metrics/dummy_histogram.h",
     "metrics/field_trial.cc",
@@ -755,6 +757,8 @@
     "task/promise/abstract_promise.h",
     "task/promise/dependent_list.cc",
     "task/promise/dependent_list.h",
+    "task/promise/finally_executor.cc",
+    "task/promise/finally_executor.h",
     "task/promise/helpers.h",
     "task/promise/no_op_promise_executor.cc",
     "task/promise/no_op_promise_executor.h",
@@ -865,6 +869,8 @@
     "task_runner_util.h",
     "template_util.h",
     "test/malloc_wrapper.h",
+    "third_party/cityhash/city.cc",
+    "third_party/cityhash/city.h",
     "third_party/dmg_fp/dmg_fp.h",
     "third_party/dmg_fp/dtoa_wrapper.cc",
     "third_party/dmg_fp/g_fmt.cc",
@@ -1368,6 +1374,7 @@
       "android/library_loader/library_loader_hooks.h",
       "android/library_loader/library_prefetcher.cc",
       "android/library_loader/library_prefetcher.h",
+      "android/library_loader/library_prefetcher_hooks.cc",
       "android/locale_utils.cc",
       "android/locale_utils.h",
       "android/memory_pressure_listener_android.cc",
@@ -2544,6 +2551,7 @@
     "message_loop/message_pump_unittest.cc",
     "message_loop/work_id_provider_unittest.cc",
     "metrics/bucket_ranges_unittest.cc",
+    "metrics/crc32_unittest.cc",
     "metrics/field_trial_params_unittest.cc",
     "metrics/field_trial_unittest.cc",
     "metrics/histogram_base_unittest.cc",
@@ -3088,6 +3096,7 @@
       "android/java/src/org/chromium/base/TraceEvent.java",
       "android/java/src/org/chromium/base/UnguessableToken.java",
       "android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
+      "android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
       "android/java/src/org/chromium/base/metrics/RecordHistogram.java",
       "android/java/src/org/chromium/base/metrics/RecordUserAction.java",
       "android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
@@ -3204,6 +3213,7 @@
       "android/java/src/org/chromium/base/compat/ApiHelperForOMR1.java",
       "android/java/src/org/chromium/base/compat/ApiHelperForP.java",
       "android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
+      "android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
       "android/java/src/org/chromium/base/library_loader/Linker.java",
       "android/java/src/org/chromium/base/library_loader/LoaderErrors.java",
       "android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java",
diff --git a/base/android/.style.yapf b/base/android/.style.yapf
new file mode 100644
index 0000000..ef24bfc
--- /dev/null
+++ b/base/android/.style.yapf
@@ -0,0 +1,6 @@
+[style]
+based_on_style = pep8
+column_limit = 80
+blank_line_before_nested_class_or_def = true
+blank_line_before_module_docstring = true
+indent_width = 2
diff --git a/base/android/java/src/org/chromium/base/LocaleUtils.java b/base/android/java/src/org/chromium/base/LocaleUtils.java
index 10d1142..a8f07e8 100644
--- a/base/android/java/src/org/chromium/base/LocaleUtils.java
+++ b/base/android/java/src/org/chromium/base/LocaleUtils.java
@@ -184,16 +184,6 @@
         return languageTag.substring(0, pos);
     }
 
-    /** @return true if the language is supported by Chrome. */
-    public static boolean isLanguageSupported(String language) {
-        for (String languageTag : BuildConfig.COMPRESSED_LOCALES) {
-            if (toLanguage(languageTag).equals(language)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**
      * @return a language tag string that represents the default locale.
      *         The language tag is well-formed IETF BCP 47 language tag with language and country
diff --git a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
index 72d116b7..ae1541f 100644
--- a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
+++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
@@ -26,20 +26,15 @@
 import org.chromium.base.Log;
 import org.chromium.base.StreamUtil;
 import org.chromium.base.StrictModeContext;
-import org.chromium.base.SysUtils;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.MainDex;
 import org.chromium.base.compat.ApiHelperForM;
-import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.base.task.PostTask;
-import org.chromium.base.task.TaskTraits;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
@@ -101,10 +96,6 @@
     // threads without a lock.
     private volatile boolean mInitialized;
 
-    // One-way switch that becomes true once
-    // {@link asyncPrefetchLibrariesToMemory} has been called.
-    private final AtomicBoolean mPrefetchLibraryHasBeenCalled = new AtomicBoolean();
-
     // Guards all fields below.
     private final Object mLock = new Object();
 
@@ -325,51 +316,6 @@
         }
     }
 
-    /**
-     * Prefetches the native libraries in a background thread.
-     *
-     * Launches a task that, through a short-lived forked process, reads a
-     * part of each page of the native library.  This is done to warm up the
-     * page cache, turning hard page faults into soft ones.
-     *
-     * This is done this way, as testing shows that fadvise(FADV_WILLNEED) is
-     * detrimental to the startup time.
-     */
-    public void asyncPrefetchLibrariesToMemory() {
-        SysUtils.logPageFaultCountToTracing();
-
-        final boolean coldStart = mPrefetchLibraryHasBeenCalled.compareAndSet(false, true);
-        // Collection should start close to the native library load, but doesn't have
-        // to be simultaneous with it. Also, don't prefetch in this case, as this would
-        // skew the results.
-        if (coldStart && CommandLine.getInstance().hasSwitch("log-native-library-residency")) {
-            // nativePeriodicallyCollectResidency() sleeps, run it on another thread,
-            // and not on the thread pool.
-            new Thread(LibraryLoader::nativePeriodicallyCollectResidency).start();
-            return;
-        }
-
-        PostTask.postTask(TaskTraits.USER_BLOCKING, () -> {
-            int percentage = nativePercentageOfResidentNativeLibraryCode();
-            try (TraceEvent e = TraceEvent.scoped("LibraryLoader.asyncPrefetchLibrariesToMemory",
-                         Integer.toString(percentage))) {
-                // Arbitrary percentage threshold. If most of the native library is already
-                // resident (likely with monochrome), don't bother creating a prefetch process.
-                boolean prefetch = coldStart && percentage < 90;
-                if (prefetch) {
-                    nativeForkAndPrefetchNativeLibrary();
-                }
-                if (percentage != -1) {
-                    String histogram = "LibraryLoader.PercentageOfResidentCodeBeforePrefetch"
-                            + (coldStart ? ".ColdStartup" : ".WarmStartup");
-                    RecordHistogram.recordPercentageHistogram(histogram, percentage);
-                }
-            }
-            // Removes a dead flag, don't remove the removal code before M77 at least.
-            ContextUtils.getAppSharedPreferences().edit().remove("dont_prefetch_libraries").apply();
-        });
-    }
-
     // Helper for loadAlreadyLocked(). Load a native shared library with the Chromium linker.
     // Sets UMA flags depending on the results of loading.
     private void loadLibraryWithCustomLinkerAlreadyLocked(
@@ -817,16 +763,4 @@
     // Get the version of the native library. This is needed so that we can check we
     // have the right version before initializing the (rest of the) JNI.
     private native String nativeGetVersionNumber();
-
-    // Finds the ranges corresponding to the native library pages, forks a new
-    // process to prefetch these pages and waits for it. The new process then
-    // terminates. This is blocking.
-    private static native void nativeForkAndPrefetchNativeLibrary();
-
-    // Returns the percentage of the native library code page that are currently reseident in
-    // memory.
-    private static native int nativePercentageOfResidentNativeLibraryCode();
-
-    // Periodically logs native library residency from this thread.
-    private static native void nativePeriodicallyCollectResidency();
 }
diff --git a/base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java b/base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java
new file mode 100644
index 0000000..c1b8dd5
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java
@@ -0,0 +1,87 @@
+// 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.
+
+package org.chromium.base.library_loader;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.SysUtils;
+import org.chromium.base.TraceEvent;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.MainDex;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.task.PostTask;
+import org.chromium.base.task.TaskTraits;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Handles native library prefetch.
+ *
+ * See also base/android/library_loader/library_prefetcher_hooks.cc, which contains
+ * the native counterpart to this class.
+ */
+@MainDex
+@JNINamespace("base::android")
+public class LibraryPrefetcher {
+    // One-way switch that becomes true once
+    // {@link asyncPrefetchLibrariesToMemory} has been called.
+    private final static AtomicBoolean sPrefetchLibraryHasBeenCalled = new AtomicBoolean();
+
+    /**
+     * Prefetches the native libraries in a background thread.
+     *
+     * Launches a task that, through a short-lived forked process, reads a
+     * part of each page of the native library.  This is done to warm up the
+     * page cache, turning hard page faults into soft ones.
+     *
+     * This is done this way, as testing shows that fadvise(FADV_WILLNEED) is
+     * detrimental to the startup time.
+     */
+    public static void asyncPrefetchLibrariesToMemory() {
+        SysUtils.logPageFaultCountToTracing();
+
+        final boolean coldStart = sPrefetchLibraryHasBeenCalled.compareAndSet(false, true);
+        // Collection should start close to the native library load, but doesn't have
+        // to be simultaneous with it. Also, don't prefetch in this case, as this would
+        // skew the results.
+        if (coldStart && CommandLine.getInstance().hasSwitch("log-native-library-residency")) {
+            // nativePeriodicallyCollectResidency() sleeps, run it on another thread,
+            // and not on the thread pool.
+            new Thread(LibraryPrefetcher::nativePeriodicallyCollectResidency).start();
+            return;
+        }
+
+        PostTask.postTask(TaskTraits.USER_BLOCKING, () -> {
+            int percentage = nativePercentageOfResidentNativeLibraryCode();
+            try (TraceEvent e =
+                            TraceEvent.scoped("LibraryPrefetcher.asyncPrefetchLibrariesToMemory",
+                                    Integer.toString(percentage))) {
+                // Arbitrary percentage threshold. If most of the native library is already
+                // resident (likely with monochrome), don't bother creating a prefetch process.
+                boolean prefetch = coldStart && percentage < 90;
+                if (prefetch) nativeForkAndPrefetchNativeLibrary();
+                if (percentage != -1) {
+                    String histogram = "LibraryLoader.PercentageOfResidentCodeBeforePrefetch"
+                            + (coldStart ? ".ColdStartup" : ".WarmStartup");
+                    RecordHistogram.recordPercentageHistogram(histogram, percentage);
+                }
+            }
+            // Removes a dead flag, don't remove the removal code before M77 at least.
+            ContextUtils.getAppSharedPreferences().edit().remove("dont_prefetch_libraries").apply();
+        });
+    }
+
+    // Finds the ranges corresponding to the native library pages, forks a new
+    // process to prefetch these pages and waits for it. The new process then
+    // terminates. This is blocking.
+    private static native void nativeForkAndPrefetchNativeLibrary();
+
+    // Returns the percentage of the native library code page that are currently reseident in
+    // memory.
+    private static native int nativePercentageOfResidentNativeLibraryCode();
+
+    // Periodically logs native library residency from this thread.
+    private static native void nativePeriodicallyCollectResidency();
+}
diff --git a/base/android/java/templates/BuildConfig.template b/base/android/java/templates/BuildConfig.template
index 2dcf476d..32bddbc 100644
--- a/base/android/java/templates/BuildConfig.template
+++ b/base/android/java/templates/BuildConfig.template
@@ -46,22 +46,6 @@
     public static MAYBE_FINAL boolean IS_CHROME_BRANDED MAYBE_FALSE;
 #endif
 
-    // Sorted list of locales that have a compressed .pak within assets.
-    // Stored as an array because AssetManager.list() is slow.
-#if defined(COMPRESSED_LOCALE_LIST)
-    public static MAYBE_FINAL String[] COMPRESSED_LOCALES = COMPRESSED_LOCALE_LIST;
-#else
-    public static MAYBE_FINAL String[] COMPRESSED_LOCALES = {};
-#endif
-
-    // Sorted list of locales that have an uncompressed .pak within assets.
-    // Stored as an array because AssetManager.list() is slow.
-#if defined(UNCOMPRESSED_LOCALE_LIST)
-    public static MAYBE_FINAL String[] UNCOMPRESSED_LOCALES = UNCOMPRESSED_LOCALE_LIST;
-#else
-    public static MAYBE_FINAL String[] UNCOMPRESSED_LOCALES = {};
-#endif
-
     // The ID of the android string resource that stores the product version.
     // This layer of indirection is necessary to make the resource dependency
     // optional for android_apk targets/base_java (ex. for cronet).
diff --git a/base/android/jni_generator/PRESUBMIT.py b/base/android/jni_generator/PRESUBMIT.py
index bc76d5b..67e84a5 100644
--- a/base/android/jni_generator/PRESUBMIT.py
+++ b/base/android/jni_generator/PRESUBMIT.py
@@ -14,24 +14,24 @@
 
   env = dict(input_api.environ)
   env.update({
-    'PYTHONPATH': base_android_jni_generator_dir,
-    'PYTHONDONTWRITEBYTECODE': '1',
+      'PYTHONPATH': base_android_jni_generator_dir,
+      'PYTHONDONTWRITEBYTECODE': '1',
   })
 
   return input_api.canned_checks.RunUnitTests(
       input_api,
       output_api,
       unit_tests=[
-        input_api.os_path.join(
-            base_android_jni_generator_dir, 'jni_generator_tests.py')
+          input_api.os_path.join(base_android_jni_generator_dir,
+                                 'jni_generator_tests.py')
       ],
       env=env,
   )
 
 
 def CheckChangeOnUpload(input_api, output_api):
-    return CommonChecks(input_api, output_api)
+  return CommonChecks(input_api, output_api)
 
 
 def CheckChangeOnCommit(input_api, output_api):
-    return CommonChecks(input_api, output_api)
+  return CommonChecks(input_api, output_api)
diff --git a/base/android/jni_generator/golden/testProxyNativesMainDex.golden b/base/android/jni_generator/golden/testProxyNativesMainDex.golden
index eaa03c2..67cc774 100644
--- a/base/android/jni_generator/golden/testProxyNativesMainDex.golden
+++ b/base/android/jni_generator/golden/testProxyNativesMainDex.golden
@@ -40,7 +40,8 @@
 JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_base_natives_GEN_1JNIMAIN_DEX(JNIEnv* env) {
   const int number_of_methods = base::size(kMethods_org_chromium_base_natives_GEN_1JNIMAIN_DEX);
 
-  base::android::ScopedJavaLocalRef<jclass> native_clazz = base::android::GetClass(env, "org/chromium/base/natives/GEN_JNI");
+  base::android::ScopedJavaLocalRef<jclass> native_clazz =
+      base::android::GetClass(env, "org/chromium/base/natives/GEN_JNI");
   if (env->RegisterNatives(
       native_clazz.obj(),
       kMethods_org_chromium_base_natives_GEN_1JNIMAIN_DEX,
diff --git a/base/android/jni_generator/golden/testProxyNativesMainDexAndNonMainDex.golden b/base/android/jni_generator/golden/testProxyNativesMainDexAndNonMainDex.golden
index e917fc9..773dc69 100644
--- a/base/android/jni_generator/golden/testProxyNativesMainDexAndNonMainDex.golden
+++ b/base/android/jni_generator/golden/testProxyNativesMainDexAndNonMainDex.golden
@@ -47,7 +47,8 @@
 JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_base_natives_GEN_1JNI(JNIEnv* env) {
   const int number_of_methods = base::size(kMethods_org_chromium_base_natives_GEN_1JNI);
 
-  base::android::ScopedJavaLocalRef<jclass> native_clazz = base::android::GetClass(env, "org/chromium/base/natives/GEN_JNI");
+  base::android::ScopedJavaLocalRef<jclass> native_clazz =
+      base::android::GetClass(env, "org/chromium/base/natives/GEN_JNI");
   if (env->RegisterNatives(
       native_clazz.obj(),
       kMethods_org_chromium_base_natives_GEN_1JNI,
@@ -70,7 +71,8 @@
 JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_base_natives_GEN_1JNIMAIN_DEX(JNIEnv* env) {
   const int number_of_methods = base::size(kMethods_org_chromium_base_natives_GEN_1JNIMAIN_DEX);
 
-  base::android::ScopedJavaLocalRef<jclass> native_clazz = base::android::GetClass(env, "org/chromium/base/natives/GEN_JNI");
+  base::android::ScopedJavaLocalRef<jclass> native_clazz =
+      base::android::GetClass(env, "org/chromium/base/natives/GEN_JNI");
   if (env->RegisterNatives(
       native_clazz.obj(),
       kMethods_org_chromium_base_natives_GEN_1JNIMAIN_DEX,
diff --git a/base/android/jni_generator/golden/testProxyNativesRegistrations.golden b/base/android/jni_generator/golden/testProxyNativesRegistrations.golden
index d43101b..16b7b25 100644
--- a/base/android/jni_generator/golden/testProxyNativesRegistrations.golden
+++ b/base/android/jni_generator/golden/testProxyNativesRegistrations.golden
@@ -60,7 +60,8 @@
 JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_base_natives_GEN_1JNI(JNIEnv* env) {
   const int number_of_methods = base::size(kMethods_org_chromium_base_natives_GEN_1JNI);
 
-  base::android::ScopedJavaLocalRef<jclass> native_clazz = base::android::GetClass(env, "org/chromium/base/natives/GEN_JNI");
+  base::android::ScopedJavaLocalRef<jclass> native_clazz =
+      base::android::GetClass(env, "org/chromium/base/natives/GEN_JNI");
   if (env->RegisterNatives(
       native_clazz.obj(),
       kMethods_org_chromium_base_natives_GEN_1JNI,
diff --git a/base/android/jni_generator/golden/testREForNatives.golden b/base/android/jni_generator/golden/testREForNatives.golden
new file mode 100644
index 0000000..c4b3c14
--- /dev/null
+++ b/base/android/jni_generator/golden/testREForNatives.golden
@@ -0,0 +1,49 @@
+// Copyright 2014 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.
+
+
+// This file is autogenerated by
+//     base/android/jni_generator/jni_generator.py
+// For
+//     foo/bar
+
+#ifndef foo_bar_JNI
+#define foo_bar_JNI
+
+#include <jni.h>
+
+#include "base/android/jni_generator/jni_generator_helper.h"
+
+
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_foo_bar[];
+const char kClassPath_foo_bar[] = "foo/bar";
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_foo_bar_clazz(nullptr);
+#ifndef foo_bar_clazz_defined
+#define foo_bar_clazz_defined
+inline jclass foo_bar_clazz(JNIEnv* env) {
+  return base::android::LazyGetClass(env, kClassPath_foo_bar, &g_foo_bar_clazz);
+}
+#endif
+
+
+// Step 2: Constants (optional).
+
+
+// Step 3: Method stubs.
+static void JNI_bar_SyncSetupEnded(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller,
+    jint nativeAndroidSyncSetupFlowHandler);
+
+JNI_GENERATOR_EXPORT void Java_foo_bar_nativeSyncSetupEnded(
+    JNIEnv* env,
+    jobject jcaller,
+    jint nativeAndroidSyncSetupFlowHandler) {
+  return JNI_bar_SyncSetupEnded(env, base::android::JavaParamRef<jobject>(env, jcaller),
+      nativeAndroidSyncSetupFlowHandler);
+}
+
+
+#endif  // foo_bar_JNI
diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py
index 6e950e5..c618455 100755
--- a/base/android/jni_generator/jni_generator.py
+++ b/base/android/jni_generator/jni_generator.py
@@ -8,25 +8,26 @@
 
 from __future__ import print_function
 
+import argparse
 import base64
 import collections
 import errno
 import hashlib
-import optparse
 import os
 import re
+import shutil
 from string import Template
 import subprocess
 import sys
+import tempfile
 import textwrap
 import zipfile
 
-CHROMIUM_SRC = os.path.join(
-    os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)
-BUILD_ANDROID_GYP = os.path.join(
-    CHROMIUM_SRC, 'build', 'android', 'gyp')
+_FILE_DIR = os.path.dirname(__file__)
+_CHROMIUM_SRC = os.path.join(_FILE_DIR, os.pardir, os.pardir, os.pardir)
+_BUILD_ANDROID_GYP = os.path.join(_CHROMIUM_SRC, 'build', 'android', 'gyp')
 
-sys.path.append(BUILD_ANDROID_GYP)
+sys.path.append(_BUILD_ANDROID_GYP)
 
 from util import build_utils
 
@@ -44,9 +45,8 @@
     r'(?P<return_type>\S*) '
     r'(?P<name>native\w+)\((?P<params>.*?)\);')
 
-_MAIN_DEX_REGEX = re.compile(
-    r'^\s*(?:@(?:\w+\.)*\w+\s+)*@MainDex\b',
-    re.MULTILINE)
+_MAIN_DEX_REGEX = re.compile(r'^\s*(?:@(?:\w+\.)*\w+\s+)*@MainDex\b',
+                             re.MULTILINE)
 
 # Matches on method declarations unlike _EXTRACT_NATIVES_REGEX
 # doesn't require name to be prefixed with native, and does not
@@ -66,11 +66,13 @@
 _WRAP_LINE_LENGTH = 100
 # WrapOutput() is fairly slow. Pre-creating TextWrappers helps a bit.
 _WRAPPERS_BY_INDENT = [
-    textwrap.TextWrapper(width=_WRAP_LINE_LENGTH, expand_tabs=False,
-                         replace_whitespace=False,
-                         subsequent_indent=' ' * (indent + 4),
-                         break_long_words=False)
-    for indent in range(50)]  # 50 chosen experimentally.
+    textwrap.TextWrapper(
+        width=_WRAP_LINE_LENGTH,
+        expand_tabs=False,
+        replace_whitespace=False,
+        subsequent_indent=' ' * (indent + 4),
+        break_long_words=False) for indent in range(50)
+]  # 50 chosen experimentally.
 
 JAVA_POD_TYPE_MAP = {
     'int': 'jint',
@@ -142,16 +144,16 @@
 
       for p in self.params[1:]:
         assert '@JCaller' not in p.annotations, ('Only the first parameter can '
-                                                'be annotated with @JCaller')
+                                                 'be annotated with @JCaller')
 
       if '@JCaller' in self.params[0].annotations:
         has_jcaller = True
 
     ptr_index = 1 if has_jcaller else 0
 
-    if (self.params and len(self.params) > ptr_index and
-        self.params[ptr_index].datatype == kwargs.get('ptr_type', 'int') and
-        self.params[ptr_index].name.startswith('native')):
+    if (self.params and len(self.params) > ptr_index
+        and self.params[ptr_index].datatype == kwargs.get('ptr_type', 'int')
+        and self.params[ptr_index].name.startswith('native')):
       self.type = 'method'
       self.p0_type = self.params[ptr_index].name[len('native'):]
       if kwargs.get('native_class_name'):
@@ -181,6 +183,7 @@
 
 
 class ConstantField(object):
+
   def __init__(self, **kwargs):
     self.name = kwargs['name']
     self.value = kwargs['value']
@@ -281,12 +284,13 @@
   """
   if not native.static:
     return _GetJNIFirstParam(native, True) + [
-            _JavaDataTypeToCForDeclaration(param.datatype) + ' ' + param.name
-            for param in native.params
-        ]
-  return [_JavaDataTypeToCForDeclaration(param.datatype) + ' ' + param.name
-      for param in native.params]
-
+        _JavaDataTypeToCForDeclaration(param.datatype) + ' ' + param.name
+        for param in native.params
+    ]
+  return [
+      _JavaDataTypeToCForDeclaration(param.datatype) + ' ' + param.name
+      for param in native.params
+  ]
 
 
 def GetParamsInStub(native):
@@ -342,8 +346,7 @@
     for match in re.finditer(re_inner, contents):
       inner = match.group('name')
       if not self._fully_qualified_class.endswith(inner):
-        self._inner_classes += [self._fully_qualified_class + '$' +
-                                     inner]
+        self._inner_classes += [self._fully_qualified_class + '$' + inner]
 
     re_additional_imports = re.compile(
         r'@JNIAdditionalImport\(\s*{?(?P<class_names>.*?)}?\s*\)')
@@ -390,11 +393,11 @@
       # Coming from javap, use the fully qualified param directly.
       return prefix + 'L' + param + ';'
 
-    for qualified_name in (object_param_list +
-                           [self._fully_qualified_class] + self._inner_classes):
-      if (qualified_name.endswith('/' + param) or
-          qualified_name.endswith('$' + param.replace('.', '$')) or
-          qualified_name == 'L' + param):
+    for qualified_name in (object_param_list + [self._fully_qualified_class] +
+                           self._inner_classes):
+      if (qualified_name.endswith('/' + param)
+          or qualified_name.endswith('$' + param.replace('.', '$'))
+          or qualified_name == 'L' + param):
         return prefix + qualified_name + ';'
 
     # Is it from an import? (e.g. referecing Class from import pkg.Class;
@@ -405,10 +408,10 @@
         # Ensure it's not an inner class.
         components = qualified_name.split('/')
         if len(components) > 2 and components[-2][0].isupper():
-          raise SyntaxError('Inner class (%s) can not be imported '
-                            'and used by JNI (%s). Please import the outer '
-                            'class and use Outer.Inner instead.' %
-                            (qualified_name, param))
+          raise SyntaxError(
+              'Inner class (%s) can not be imported '
+              'and used by JNI (%s). Please import the outer '
+              'class and use Outer.Inner instead.' % (qualified_name, param))
         return prefix + qualified_name + ';'
 
     # Is it an inner class from an outer class import? (e.g. referencing
@@ -423,9 +426,8 @@
       raise SyntaxError('Inner class (%s) can not be '
                         'used directly by JNI. Please import the outer '
                         'class, probably:\n'
-                        'import %s.%s;' %
-                        (param, self._package.replace('/', '.'),
-                         outer.replace('/', '.')))
+                        'import %s.%s;' % (param, self._package.replace(
+                            '/', '.'), outer.replace('/', '.')))
 
     self._CheckImplicitImports(param)
 
@@ -450,8 +452,8 @@
     if not self._implicit_imports:
       # This file was generated from android.jar and lists
       # all classes that are implicitly imported.
-      with open(os.path.join(os.path.dirname(__file__),
-                             'android_jar.classes'), 'r') as f:
+      android_jar_path = os.path.join(_FILE_DIR, 'android_jar.classes')
+      with open(android_jar_path) as f:
         self._implicit_imports = f.readlines()
     for implicit_import in self._implicit_imports:
       implicit_import = implicit_import.strip().replace('.class', '')
@@ -459,8 +461,7 @@
       if implicit_import.endswith('.' + param):
         raise SyntaxError('Ambiguous class (%s) can not be used directly '
                           'by JNI.\nPlease import it, probably:\n\n'
-                          'import %s;' %
-                          (param, implicit_import))
+                          'import %s;' % (param, implicit_import))
 
   def Signature(self, params, returns):
     """Returns the JNI signature for the given datatypes."""
@@ -528,8 +529,9 @@
   matches = re.findall(re_package, contents)
   if not matches:
     raise SyntaxError('Unable to find "package" line in %s' % java_file_name)
-  return (matches[0].replace('.', '/') + '/' +
-          os.path.splitext(os.path.basename(java_file_name))[0])
+  class_path = matches[0].replace('.', '/')
+  class_name = os.path.splitext(os.path.basename(java_file_name))[0]
+  return class_path + '/' + class_name
 
 
 def ExtractNatives(contents, ptr_type):
@@ -565,26 +567,29 @@
   escaped = fully_qualified_class.replace('_', '_1')
   return escaped.replace('/', '_').replace('$', '_00024')
 
+
 def GetRegistrationFunctionName(fully_qualified_class):
   """Returns the register name with a given class."""
   return 'RegisterNative_' + EscapeClassName(fully_qualified_class)
 
 
 def GetStaticCastForReturnType(return_type):
-  type_map = { 'String' : 'jstring',
-               'java/lang/String' : 'jstring',
-               'Class': 'jclass',
-               'java/lang/Class': 'jclass',
-               'Throwable': 'jthrowable',
-               'java/lang/Throwable': 'jthrowable',
-               'boolean[]': 'jbooleanArray',
-               'byte[]': 'jbyteArray',
-               'char[]': 'jcharArray',
-               'short[]': 'jshortArray',
-               'int[]': 'jintArray',
-               'long[]': 'jlongArray',
-               'float[]': 'jfloatArray',
-               'double[]': 'jdoubleArray' }
+  type_map = {
+      'String': 'jstring',
+      'java/lang/String': 'jstring',
+      'Class': 'jclass',
+      'java/lang/Class': 'jclass',
+      'Throwable': 'jthrowable',
+      'java/lang/Throwable': 'jthrowable',
+      'boolean[]': 'jbooleanArray',
+      'byte[]': 'jbyteArray',
+      'char[]': 'jcharArray',
+      'short[]': 'jshortArray',
+      'int[]': 'jintArray',
+      'long[]': 'jlongArray',
+      'float[]': 'jfloatArray',
+      'double[]': 'jdoubleArray'
+  }
   return_type = _StripGenerics(return_type)
   ret = type_map.get(return_type, None)
   if ret:
@@ -598,17 +603,18 @@
   """Maps the types availabe via env->Call__Method."""
   if is_constructor:
     return 'NewObject'
-  env_call_map = {'boolean': 'Boolean',
-                  'byte': 'Byte',
-                  'char': 'Char',
-                  'short': 'Short',
-                  'int': 'Int',
-                  'long': 'Long',
-                  'float': 'Float',
-                  'void': 'Void',
-                  'double': 'Double',
-                  'Object': 'Object',
-                 }
+  env_call_map = {
+      'boolean': 'Boolean',
+      'byte': 'Byte',
+      'char': 'Char',
+      'short': 'Short',
+      'int': 'Int',
+      'long': 'Long',
+      'float': 'Float',
+      'void': 'Void',
+      'double': 'Double',
+      'Object': 'Object',
+  }
   call = env_call_map.get(return_type, 'Object')
   if is_static:
     call = 'Static' + call
@@ -677,7 +683,6 @@
 # Regex to match the JNI types that should be wrapped in a JavaRef.
 RE_SCOPED_JNI_TYPES = re.compile('jobject|jclass|jstring|jthrowable|.*Array')
 
-
 # Regex to match a string like "@CalledByNative public void foo(int bar)".
 RE_CALLED_BY_NATIVE = re.compile(
     r'@CalledByNative(?P<Unchecked>(?:Unchecked)?)(?:\("(?P<annotation>.*)"\))?'
@@ -690,6 +695,7 @@
     r'\s*(?P<name>\w+)'
     r'\s*\((?P<params>[^\)]*)\)')
 
+
 # Removes empty lines that are indented (i.e. start with 2x spaces).
 def RemoveIndentedEmptyLines(string):
   return re.sub('^(?: {2})+$\n', '', string, flags=re.MULTILINE)
@@ -721,15 +727,17 @@
     else:
       is_constructor = False
 
-    called_by_natives += [CalledByNative(
-        system_class=False,
-        unchecked='Unchecked' in match.group('Unchecked'),
-        static='static' in match.group('prefix'),
-        java_class_name=match.group('annotation') or '',
-        return_type=return_type,
-        name=name,
-        is_constructor=is_constructor,
-        params=JniParams.Parse(match.group('params')))]
+    called_by_natives += [
+        CalledByNative(
+            system_class=False,
+            unchecked='Unchecked' in match.group('Unchecked'),
+            static='static' in match.group('prefix'),
+            java_class_name=match.group('annotation') or '',
+            return_type=return_type,
+            name=name,
+            is_constructor=is_constructor,
+            params=JniParams.Parse(match.group('params')))
+    ]
   # Check for any @CalledByNative occurrences that weren't matched.
   unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n')
   for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]):
@@ -754,6 +762,7 @@
       return ''
     else:
       return s
+
   return _COMMENT_REMOVER_REGEX.sub(replacer, contents)
 
 
@@ -765,8 +774,7 @@
     self.namespace = options.namespace
     for line in contents:
       class_name = re.match(
-          '.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)',
-          line)
+          '.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)', line)
       if class_name:
         self.fully_qualified_class = class_name.group('class_name')
         break
@@ -785,15 +793,17 @@
       match = re.match(re_method, content)
       if not match:
         continue
-      self.called_by_natives += [CalledByNative(
-          system_class=True,
-          unchecked=False,
-          static='static' in match.group('prefix'),
-          java_class_name='',
-          return_type=match.group('return_type').replace('.', '/'),
-          name=match.group('name'),
-          params=JniParams.Parse(match.group('params').replace('.', '/')),
-          signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))]
+      self.called_by_natives += [
+          CalledByNative(
+              system_class=True,
+              unchecked=False,
+              static='static' in match.group('prefix'),
+              java_class_name='',
+              return_type=match.group('return_type').replace('.', '/'),
+              name=match.group('name'),
+              params=JniParams.Parse(match.group('params').replace('.', '/')),
+              signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))
+      ]
     re_constructor = re.compile('(.*?)public ' +
                                 self.fully_qualified_class.replace('/', '.') +
                                 '\((?P<params>.*?)\)')
@@ -801,19 +811,20 @@
       match = re.match(re_constructor, content)
       if not match:
         continue
-      self.called_by_natives += [CalledByNative(
-          system_class=True,
-          unchecked=False,
-          static=False,
-          java_class_name='',
-          return_type=self.fully_qualified_class,
-          name='Constructor',
-          params=JniParams.Parse(match.group('params').replace('.', '/')),
-          signature=JniParams.ParseJavaPSignature(contents[lineno + 1]),
-          is_constructor=True)]
-    self.called_by_natives = MangleCalledByNatives(self.jni_params,
-                                                   self.called_by_natives,
-                                                   options.always_mangle)
+      self.called_by_natives += [
+          CalledByNative(
+              system_class=True,
+              unchecked=False,
+              static=False,
+              java_class_name='',
+              return_type=self.fully_qualified_class,
+              name='Constructor',
+              params=JniParams.Parse(match.group('params').replace('.', '/')),
+              signature=JniParams.ParseJavaPSignature(contents[lineno + 1]),
+              is_constructor=True)
+      ]
+    self.called_by_natives = MangleCalledByNatives(
+        self.jni_params, self.called_by_natives, options.always_mangle)
     self.constant_fields = []
     re_constant_field = re.compile('.*?public static final int (?P<name>.*?);')
     re_constant_field_value = re.compile(
@@ -827,8 +838,7 @@
         value = re.match(re_constant_field_value, contents[lineno + 3])
       if value:
         self.constant_fields.append(
-            ConstantField(name=match.group('name'),
-                          value=value.group('value')))
+            ConstantField(name=match.group('name'), value=value.group('value')))
 
     self.inl_header_file_generator = InlHeaderFileGenerator(
         self.namespace, self.fully_qualified_class, [], self.called_by_natives,
@@ -840,12 +850,12 @@
   @staticmethod
   def CreateFromClass(class_file, options):
     class_name = os.path.splitext(os.path.basename(class_file))[0]
-    p = subprocess.Popen(args=[options.javap, '-c', '-verbose',
-                               '-s', class_name],
-                         cwd=os.path.dirname(class_file),
-                         stdout=subprocess.PIPE,
-                         stderr=subprocess.PIPE,
-                         universal_newlines=True)
+    p = subprocess.Popen(
+        args=[options.javap, '-c', '-verbose', '-s', class_name],
+        cwd=os.path.dirname(class_file),
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        universal_newlines=True)
     stdout, _ = p.communicate()
     jni_from_javap = JNIFromJavaP(stdout.split('\n'), options)
     return jni_from_javap
@@ -949,8 +959,7 @@
     self.jni_params.ExtractImportsAndInnerClasses(contents)
     jni_namespace = ExtractJNINamespace(contents) or options.namespace
     natives = ExtractNatives(contents, options.ptr_type)
-    called_by_natives = ExtractCalledByNatives(self.jni_params,
-                                               contents,
+    called_by_natives = ExtractCalledByNatives(self.jni_params, contents,
                                                options.always_mangle)
 
     natives += ProxyHelpers.ExtractStaticProxyNatives(
@@ -958,8 +967,8 @@
         options.use_proxy_hash)
 
     if len(natives) == 0 and len(called_by_natives) == 0:
-      raise SyntaxError('Unable to find any JNI methods for %s.' %
-                        fully_qualified_class)
+      raise SyntaxError(
+          'Unable to find any JNI methods for %s.' % fully_qualified_class)
     inl_header_file_generator = InlHeaderFileGenerator(
         jni_namespace, fully_qualified_class, natives, called_by_natives, [],
         self.jni_params, options)
@@ -972,8 +981,8 @@
   def CreateFromFile(java_file_name, options):
     with open(java_file_name) as f:
       contents = f.read()
-    fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name,
-                                                               contents)
+    fully_qualified_class = ExtractFullyQualifiedJavaClassName(
+        java_file_name, contents)
     return JNIFromJavaSource(contents, fully_qualified_class, options)
 
 
@@ -1097,7 +1106,6 @@
     self.helper = HeaderFileGeneratorHelper(
         self.class_name, fully_qualified_class, self.options.use_proxy_hash)
 
-
   def GetContent(self):
     """Returns the content of the JNI binding file."""
     template = Template("""\
@@ -1142,13 +1150,13 @@
     open_namespace = self.GetOpenNamespaceString()
     if open_namespace:
       close_namespace = self.GetCloseNamespaceString()
-      values['METHOD_STUBS'] = '\n'.join([
-            open_namespace, values['METHOD_STUBS'], close_namespace])
+      values['METHOD_STUBS'] = '\n'.join(
+          [open_namespace, values['METHOD_STUBS'], close_namespace])
 
       constant_fields = values['CONSTANT_FIELDS']
       if constant_fields:
-        values['CONSTANT_FIELDS'] = '\n'.join([
-            open_namespace, constant_fields, close_namespace])
+        values['CONSTANT_FIELDS'] = '\n'.join(
+            [open_namespace, constant_fields, close_namespace])
 
     return WrapOutput(template.substitute(values))
 
@@ -1175,8 +1183,10 @@
     return '\n'.join(ret)
 
   def GetLazyCalledByNativeMethodStubs(self):
-    return [self.GetLazyCalledByNativeMethodStub(called_by_native)
-            for called_by_native in self.called_by_natives]
+    return [
+        self.GetLazyCalledByNativeMethodStub(called_by_native)
+        for called_by_native in self.called_by_natives
+    ]
 
   def GetIncludesString(self):
     if not self.options.includes:
@@ -1186,31 +1196,35 @@
 
   def GetOpenNamespaceString(self):
     if self.namespace:
-      all_namespaces = ['namespace %s {' % ns
-                        for ns in self.namespace.split('::')]
+      all_namespaces = [
+          'namespace %s {' % ns for ns in self.namespace.split('::')
+      ]
       return '\n'.join(all_namespaces) + '\n'
     return ''
 
   def GetCloseNamespaceString(self):
     if self.namespace:
-      all_namespaces = ['}  // namespace %s' % ns
-                        for ns in self.namespace.split('::')]
+      all_namespaces = [
+          '}  // namespace %s' % ns for ns in self.namespace.split('::')
+      ]
       all_namespaces.reverse()
       return '\n' + '\n'.join(all_namespaces)
     return ''
 
   def GetCalledByNativeParamsInDeclaration(self, called_by_native):
     return ',\n    '.join([
-        JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' +
-        param.name
-        for param in called_by_native.params])
+        JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' + param.name
+        for param in called_by_native.params
+    ])
 
   def GetJavaParamRefForCall(self, c_type, name):
     return Template(
         'base::android::JavaParamRef<${TYPE}>(env, ${NAME})').substitute({
-        'TYPE': c_type,
-        'NAME': name,
-    })
+            'TYPE':
+            c_type,
+            'NAME':
+            name,
+        })
 
   def GetJNIFirstParamForCall(self, native):
     c_type = _GetJNIFirstParamType(native)
@@ -1275,8 +1289,8 @@
     post_call = ''
     if re.match(RE_SCOPED_JNI_TYPES, return_type):
       post_call = '.Release()'
-      return_declaration = ('base::android::ScopedJavaLocalRef<' + return_type +
-                            '>')
+      return_declaration = (
+          'base::android::ScopedJavaLocalRef<' + return_type + '>')
     profiling_entered_native = ''
     if self.options.enable_profiling:
       profiling_entered_native = '  JNI_LINK_SAVED_FRAME_POINTER;\n'
@@ -1307,7 +1321,7 @@
       })
       if self.options.enable_tracing:
         values['TRACE_EVENT'] = self.GetTraceEventForNameTemplate(
-            namespace_qual + '${P0_TYPE}::${NAME}', values);
+            namespace_qual + '${P0_TYPE}::${NAME}', values)
       template = Template("""\
 JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(
     JNIEnv* env,
@@ -1407,8 +1421,8 @@
     if called_by_native.signature:
       jni_signature = called_by_native.signature
     else:
-      jni_signature = self.jni_params.Signature(
-          called_by_native.params, jni_return_type)
+      jni_signature = self.jni_params.Signature(called_by_native.params,
+                                                jni_return_type)
     java_name_full = java_class.replace('/', '.') + '.' + jni_name
     return {
         'JAVA_CLASS_ONLY': java_class_only,
@@ -1503,35 +1517,6 @@
   return '\n'.join(ret)
 
 
-def ExtractJarInputFile(jar_file, input_file, out_dir):
-  """Extracts input file from jar and returns the filename.
-
-  The input file is extracted to the same directory that the generated jni
-  headers will be placed in.  This is passed as an argument to script.
-
-  Args:
-    jar_file: the jar file containing the input files to extract.
-    input_files: the list of files to extract from the jar file.
-    out_dir: the name of the directories to extract to.
-
-  Returns:
-    the name of extracted input file.
-  """
-  jar_file = zipfile.ZipFile(jar_file)
-
-  out_dir = os.path.join(out_dir, os.path.dirname(input_file))
-  try:
-    os.makedirs(out_dir)
-  except OSError as e:
-    if e.errno != errno.EEXIST:
-      raise
-  extracted_file_name = os.path.join(out_dir, os.path.basename(input_file))
-  with open(extracted_file_name, 'wb') as outfile:
-    outfile.write(jar_file.read(input_file))
-
-  return extracted_file_name
-
-
 def GenerateJNIHeader(input_file, output_file, options):
   try:
     if os.path.splitext(input_file)[1] == '.class':
@@ -1561,74 +1546,94 @@
   return os.sep.join(script_components[base_index:])
 
 
-def main(argv):
+def main():
   usage = """usage: %prog [OPTIONS]
 This script will parse the given java source code extracting the native
 declarations and print the header file to stdout (or a file).
 See SampleForTests.java for more details.
   """
-  option_parser = optparse.OptionParser(usage=usage)
+  parser = argparse.ArgumentParser(usage=usage)
 
-  option_parser.add_option('-j', '--jar_file', dest='jar_file',
-                           help='Extract the list of input files from'
-                           ' a specified jar file.'
-                           ' Uses javap to extract the methods from a'
-                           ' pre-compiled class. --input should point'
-                           ' to pre-compiled Java .class files.')
-  option_parser.add_option('-n', dest='namespace',
-                           help='Uses as a namespace in the generated header '
-                           'instead of the javap class name, or when there is '
-                           'no JNINamespace annotation in the java source.')
-  option_parser.add_option('--input_file',
-                           help='Single input file name. The output file name '
-                           'will be derived from it. Must be used with '
-                           '--output_dir.')
-  option_parser.add_option('--output_dir',
-                           help='The output directory. Must be used with '
-                           '--input')
-  option_parser.add_option('--script_name', default=GetScriptName(),
-                           help='The name of this script in the generated '
-                           'header.')
-  option_parser.add_option('--includes',
-                           help='The comma-separated list of header files to '
-                           'include in the generated header.')
-  option_parser.add_option('--ptr_type', default='int',
-                           type='choice', choices=['int', 'long'],
-                           help='The type used to represent native pointers in '
-                           'Java code. For 32-bit, use int; '
-                           'for 64-bit, use long.')
-  option_parser.add_option('--cpp', default='cpp',
-                           help='The path to cpp command.')
-  option_parser.add_option('--javap', default='javap',
-                           help='The path to javap command.')
-  option_parser.add_option('--enable_profiling', action='store_true',
-                           help='Add additional profiling instrumentation.')
-  option_parser.add_option('--enable_tracing', action='store_true',
-                           help='Add TRACE_EVENTs to generated functions.')
-  option_parser.add_option('--always_mangle', action='store_true',
-                           help='Mangle all function names')
-  option_parser.add_option(
+  parser.add_argument(
+      '-j',
+      '--jar_file',
+      dest='jar_file',
+      help='Extract the list of input files from'
+      ' a specified jar file.'
+      ' Uses javap to extract the methods from a'
+      ' pre-compiled class. --input should point'
+      ' to pre-compiled Java .class files.')
+  parser.add_argument(
+      '-n',
+      dest='namespace',
+      help='Uses as a namespace in the generated header '
+      'instead of the javap class name, or when there is '
+      'no JNINamespace annotation in the java source.')
+  parser.add_argument(
+      '--input_file',
+      action='append',
+      required=True,
+      dest='input_files',
+      help='Input file names, or paths within a .jar if '
+      '--jar-file is used.')
+  parser.add_argument(
+      '--output_file',
+      action='append',
+      dest='output_files',
+      help='Output file names.')
+  parser.add_argument(
+      '--script_name',
+      default=GetScriptName(),
+      help='The name of this script in the generated '
+      'header.')
+  parser.add_argument(
+      '--includes',
+      help='The comma-separated list of header files to '
+      'include in the generated header.')
+  parser.add_argument(
+      '--ptr_type',
+      default='int',
+      choices=['int', 'long'],
+      help='The type used to represent native pointers in '
+      'Java code. For 32-bit, use int; '
+      'for 64-bit, use long.')
+  parser.add_argument('--cpp', default='cpp', help='The path to cpp command.')
+  parser.add_argument(
+      '--javap', default='javap', help='The path to javap command.')
+  parser.add_argument(
+      '--enable_profiling',
+      action='store_true',
+      help='Add additional profiling instrumentation.')
+  parser.add_argument(
+      '--enable_tracing',
+      action='store_true',
+      help='Add TRACE_EVENTs to generated functions.')
+  parser.add_argument(
+      '--always_mangle', action='store_true', help='Mangle all function names')
+  parser.add_argument(
       '--use_proxy_hash',
       action='store_true',
       help='Hashes the native declaration of methods used '
       'in @JniNatives interface. And uses a shorter name and package'
       ' than GEN_JNI.')
-  options, args = option_parser.parse_args(argv)
-  if options.jar_file:
-    input_file = ExtractJarInputFile(options.jar_file, options.input_file,
-                                     options.output_dir)
-  elif options.input_file:
-    input_file = options.input_file
-  else:
-    option_parser.print_help()
-    print('\nError: Must specify --jar_file or --input_file.')
-    return 1
-  output_file = None
-  if options.output_dir:
-    root_name = os.path.splitext(os.path.basename(input_file))[0]
-    output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
-  GenerateJNIHeader(input_file, output_file, options)
+  args = parser.parse_args()
+  input_files = args.input_files
+  output_files = args.output_files
+  if not output_files:
+    output_files = [None] * len(input_files)
+
+  temp_dir = tempfile.mkdtemp()
+  try:
+    if args.jar_file:
+      with zipfile.ZipFile(args.jar_file) as z:
+        z.extractall(temp_dir, input_files)
+      input_files = [os.path.join(temp_dir, f) for f in input_files]
+
+    for java_path, header_path in zip(input_files, output_files):
+      GenerateJNIHeader(java_path, header_path, args)
+  finally:
+    shutil.rmtree(temp_dir)
 
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv))
+  sys.exit(main())
diff --git a/base/android/jni_generator/jni_generator_tests.py b/base/android/jni_generator/jni_generator_tests.py
index c99890c..8ff7dfc6 100755
--- a/base/android/jni_generator/jni_generator_tests.py
+++ b/base/android/jni_generator/jni_generator_tests.py
@@ -26,25 +26,23 @@
 from jni_generator import NativeMethod
 from jni_generator import Param
 
-
-SCRIPT_NAME = 'base/android/jni_generator/jni_generator.py'
-INCLUDES = (
-    'base/android/jni_generator/jni_generator_helper.h'
-)
+_SCRIPT_NAME = 'base/android/jni_generator/jni_generator.py'
+_INCLUDES = ('base/android/jni_generator/jni_generator_helper.h')
 _JAVA_SRC_DIR = os.path.join('java', 'src', 'org', 'chromium', 'example',
                              'jni_generator')
 
 # Set this environment variable in order to regenerate the golden text
 # files.
-REBASELINE_ENV = 'REBASELINE'
+_REBASELINE_ENV = 'REBASELINE'
+
 
 class TestOptions(object):
   """The mock options object which is passed to the jni_generator.py script."""
 
   def __init__(self):
     self.namespace = None
-    self.script_name = SCRIPT_NAME
-    self.includes = INCLUDES
+    self.script_name = _SCRIPT_NAME
+    self.includes = _INCLUDES
     self.ptr_type = 'long'
     self.cpp = 'cpp'
     self.javap = 'javap'
@@ -99,13 +97,13 @@
 
   def AssertObjEquals(self, first, second):
     if isinstance(first, str):
-      return self.assertEqual(first,second)
+      return self.assertEqual(first, second)
     dict_first = first.__dict__
     dict_second = second.__dict__
     self.assertEqual(dict_first.keys(), dict_second.keys())
     for key, value in dict_first.items():
-      if (type(value) is list and len(value) and
-          isinstance(type(value[0]), object)):
+      if (type(value) is list and len(value)
+          and isinstance(type(value[0]), object)):
         self.AssertListEquals(value, second.__getattribute__(key))
       else:
         actual = second.__getattribute__(key)
@@ -128,10 +126,10 @@
 
     def FilterText(text):
       return [
-          l.strip()
-          for l in text.split('\n')
+          l.strip() for l in text.split('\n')
           if not l.startswith('// Copyright')
       ]
+
     stripped_golden = FilterText(golden_text)
     stripped_generated = FilterText(generated_text)
     if stripped_golden == stripped_generated:
@@ -164,7 +162,7 @@
           'test* method, not %s' % caller)
       golden_file = '%s%s.golden' % (caller, suffix)
     golden_text = self._ReadGoldenFile(golden_file)
-    if os.environ.get(REBASELINE_ENV):
+    if os.environ.get(_REBASELINE_ENV):
       if golden_text != generated_text:
         with open(self._JoinGoldenPath(golden_file), 'w') as f:
           f.write(generated_text)
@@ -180,9 +178,11 @@
 class TestGenerator(BaseTest):
 
   def testInspectCaller(self):
+
     def willRaise():
       # This function can only be called from a test* method.
       self.AssertGoldenTextEquals('')
+
     self.assertRaises(AssertionError, willRaise)
 
   def testNatives(self):
@@ -223,128 +223,141 @@
     jni_params.ExtractImportsAndInnerClasses(test_data)
     natives = jni_generator.ExtractNatives(test_data, 'int')
     golden_natives = [
-        NativeMethod(return_type='int', static=False,
-                     name='Init',
-                     params=[],
-                     java_class_name=None,
-                     type='function'),
-        NativeMethod(return_type='void', static=False, name='Destroy',
-                     params=[Param(datatype='int',
-                                   name='nativeChromeBrowserProvider')],
-                     java_class_name=None,
-                     type='method',
-                     p0_type='ChromeBrowserProvider'),
-        NativeMethod(return_type='long', static=False, name='AddBookmark',
-                     params=[Param(datatype='int',
-                                   name='nativeChromeBrowserProvider'),
-                             Param(datatype='String',
-                                   name='url'),
-                             Param(datatype='String',
-                                   name='title'),
-                             Param(datatype='boolean',
-                                   name='isFolder'),
-                             Param(datatype='long',
-                                   name='parentId')],
-                     java_class_name=None,
-                     type='method',
-                     p0_type='ChromeBrowserProvider'),
-        NativeMethod(return_type='String', static=True,
-                     name='GetDomainAndRegistry',
-                     params=[Param(datatype='String',
-                                   name='url')],
-                     java_class_name=None,
-                     type='function'),
-        NativeMethod(return_type='void', static=True,
-                     name='CreateHistoricalTabFromState',
-                     params=[Param(datatype='byte[]',
-                                   name='state'),
-                             Param(datatype='int',
-                                   name='tab_index')],
-                     java_class_name=None,
-                     type='function'),
-        NativeMethod(return_type='byte[]', static=False,
-                     name='GetStateAsByteArray',
-                     params=[Param(datatype='View', name='view')],
-                     java_class_name=None,
-                     type='function'),
-        NativeMethod(return_type='String[]', static=True,
-                     name='GetAutofillProfileGUIDs', params=[],
-                     java_class_name=None,
-                     type='function'),
-        NativeMethod(return_type='void', static=False,
-                     name='SetRecognitionResults',
-                     params=[Param(datatype='int', name='sessionId'),
-                             Param(datatype='String[]', name='results')],
-                     java_class_name=None,
-                     type='function'),
-        NativeMethod(return_type='long', static=False,
-                     name='AddBookmarkFromAPI',
-                     params=[Param(datatype='int',
-                                   name='nativeChromeBrowserProvider'),
-                             Param(datatype='String',
-                                   name='url'),
-                             Param(datatype='Long',
-                                   name='created'),
-                             Param(datatype='Boolean',
-                                   name='isBookmark'),
-                             Param(datatype='Long',
-                                   name='date'),
-                             Param(datatype='byte[]',
-                                   name='favicon'),
-                             Param(datatype='String',
-                                   name='title'),
-                             Param(datatype='Integer',
-                                   name='visits')],
-                     java_class_name=None,
-                     type='method',
-                     p0_type='ChromeBrowserProvider'),
-        NativeMethod(return_type='int', static=False,
-                     name='FindAll',
-                     params=[Param(datatype='String',
-                                   name='find')],
-                     java_class_name=None,
-                     type='function'),
-        NativeMethod(return_type='OnFrameAvailableListener', static=True,
-                     name='GetInnerClass',
-                     params=[],
-                     java_class_name=None,
-                     type='function'),
-        NativeMethod(return_type='Bitmap',
-                     static=False,
-                     name='QueryBitmap',
-                     params=[Param(datatype='int',
-                                   name='nativeChromeBrowserProvider'),
-                             Param(datatype='String[]',
-                                   name='projection'),
-                             Param(datatype='String',
-                                   name='selection'),
-                             Param(datatype='String[]',
-                                   name='selectionArgs'),
-                             Param(datatype='String',
-                                   name='sortOrder'),
-                            ],
-                     java_class_name=None,
-                     type='method',
-                     p0_type='ChromeBrowserProvider'),
-        NativeMethod(return_type='void', static=False,
-                     name='GotOrientation',
-                     params=[Param(datatype='int',
-                                   name='nativeDataFetcherImplAndroid'),
-                             Param(datatype='double',
-                                   name='alpha'),
-                             Param(datatype='double',
-                                   name='beta'),
-                             Param(datatype='double',
-                                   name='gamma'),
-                            ],
-                     java_class_name=None,
-                     type='method',
-                     p0_type='content::DataFetcherImplAndroid'),
-        NativeMethod(return_type='Throwable', static=True,
-                     name='MessWithJavaException',
-                     params=[Param(datatype='Throwable', name='e')],
-                     java_class_name=None,
-                     type='function')
+        NativeMethod(
+            return_type='int',
+            static=False,
+            name='Init',
+            params=[],
+            java_class_name=None,
+            type='function'),
+        NativeMethod(
+            return_type='void',
+            static=False,
+            name='Destroy',
+            params=[Param(datatype='int', name='nativeChromeBrowserProvider')],
+            java_class_name=None,
+            type='method',
+            p0_type='ChromeBrowserProvider'),
+        NativeMethod(
+            return_type='long',
+            static=False,
+            name='AddBookmark',
+            params=[
+                Param(datatype='int', name='nativeChromeBrowserProvider'),
+                Param(datatype='String', name='url'),
+                Param(datatype='String', name='title'),
+                Param(datatype='boolean', name='isFolder'),
+                Param(datatype='long', name='parentId')
+            ],
+            java_class_name=None,
+            type='method',
+            p0_type='ChromeBrowserProvider'),
+        NativeMethod(
+            return_type='String',
+            static=True,
+            name='GetDomainAndRegistry',
+            params=[Param(datatype='String', name='url')],
+            java_class_name=None,
+            type='function'),
+        NativeMethod(
+            return_type='void',
+            static=True,
+            name='CreateHistoricalTabFromState',
+            params=[
+                Param(datatype='byte[]', name='state'),
+                Param(datatype='int', name='tab_index')
+            ],
+            java_class_name=None,
+            type='function'),
+        NativeMethod(
+            return_type='byte[]',
+            static=False,
+            name='GetStateAsByteArray',
+            params=[Param(datatype='View', name='view')],
+            java_class_name=None,
+            type='function'),
+        NativeMethod(
+            return_type='String[]',
+            static=True,
+            name='GetAutofillProfileGUIDs',
+            params=[],
+            java_class_name=None,
+            type='function'),
+        NativeMethod(
+            return_type='void',
+            static=False,
+            name='SetRecognitionResults',
+            params=[
+                Param(datatype='int', name='sessionId'),
+                Param(datatype='String[]', name='results')
+            ],
+            java_class_name=None,
+            type='function'),
+        NativeMethod(
+            return_type='long',
+            static=False,
+            name='AddBookmarkFromAPI',
+            params=[
+                Param(datatype='int', name='nativeChromeBrowserProvider'),
+                Param(datatype='String', name='url'),
+                Param(datatype='Long', name='created'),
+                Param(datatype='Boolean', name='isBookmark'),
+                Param(datatype='Long', name='date'),
+                Param(datatype='byte[]', name='favicon'),
+                Param(datatype='String', name='title'),
+                Param(datatype='Integer', name='visits')
+            ],
+            java_class_name=None,
+            type='method',
+            p0_type='ChromeBrowserProvider'),
+        NativeMethod(
+            return_type='int',
+            static=False,
+            name='FindAll',
+            params=[Param(datatype='String', name='find')],
+            java_class_name=None,
+            type='function'),
+        NativeMethod(
+            return_type='OnFrameAvailableListener',
+            static=True,
+            name='GetInnerClass',
+            params=[],
+            java_class_name=None,
+            type='function'),
+        NativeMethod(
+            return_type='Bitmap',
+            static=False,
+            name='QueryBitmap',
+            params=[
+                Param(datatype='int', name='nativeChromeBrowserProvider'),
+                Param(datatype='String[]', name='projection'),
+                Param(datatype='String', name='selection'),
+                Param(datatype='String[]', name='selectionArgs'),
+                Param(datatype='String', name='sortOrder'),
+            ],
+            java_class_name=None,
+            type='method',
+            p0_type='ChromeBrowserProvider'),
+        NativeMethod(
+            return_type='void',
+            static=False,
+            name='GotOrientation',
+            params=[
+                Param(datatype='int', name='nativeDataFetcherImplAndroid'),
+                Param(datatype='double', name='alpha'),
+                Param(datatype='double', name='beta'),
+                Param(datatype='double', name='gamma'),
+            ],
+            java_class_name=None,
+            type='method',
+            p0_type='content::DataFetcherImplAndroid'),
+        NativeMethod(
+            return_type='Throwable',
+            static=True,
+            name='MessWithJavaException',
+            params=[Param(datatype='Throwable', name='e')],
+            java_class_name=None,
+            type='function')
     ]
     self.AssertListEquals(golden_natives, natives)
     h1 = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni',
@@ -373,10 +386,13 @@
     """
     natives = jni_generator.ExtractNatives(test_data, 'int')
     golden_natives = [
-        NativeMethod(return_type='int', static=False,
-                     name='Init', params=[],
-                     java_class_name='MyInnerClass',
-                     type='function')
+        NativeMethod(
+            return_type='int',
+            static=False,
+            name='Init',
+            params=[],
+            java_class_name='MyInnerClass',
+            type='function')
     ]
     self.AssertListEquals(golden_natives, natives)
     jni_params = jni_generator.JniParams('')
@@ -398,14 +414,20 @@
     """
     natives = jni_generator.ExtractNatives(test_data, 'int')
     golden_natives = [
-        NativeMethod(return_type='int', static=False,
-                     name='Init', params=[],
-                     java_class_name='MyInnerClass',
-                     type='function'),
-        NativeMethod(return_type='int', static=False,
-                     name='Init', params=[],
-                     java_class_name='MyOtherInnerClass',
-                     type='function')
+        NativeMethod(
+            return_type='int',
+            static=False,
+            name='Init',
+            params=[],
+            java_class_name='MyInnerClass',
+            type='function'),
+        NativeMethod(
+            return_type='int',
+            static=False,
+            name='Init',
+            params=[],
+            java_class_name='MyOtherInnerClass',
+            type='function')
     ]
     self.AssertListEquals(golden_natives, natives)
     jni_params = jni_generator.JniParams('')
@@ -426,14 +448,20 @@
     """
     natives = jni_generator.ExtractNatives(test_data, 'int')
     golden_natives = [
-        NativeMethod(return_type='int', static=False,
-                     name='Init', params=[],
-                     java_class_name=None,
-                     type='function'),
-        NativeMethod(return_type='int', static=False,
-                     name='Init', params=[],
-                     java_class_name='MyOtherInnerClass',
-                     type='function')
+        NativeMethod(
+            return_type='int',
+            static=False,
+            name='Init',
+            params=[],
+            java_class_name=None,
+            type='function'),
+        NativeMethod(
+            return_type='int',
+            static=False,
+            name='Init',
+            params=[],
+            java_class_name='MyOtherInnerClass',
+            type='function')
     ]
     self.AssertListEquals(golden_natives, natives)
     jni_params = jni_generator.JniParams('')
@@ -559,11 +587,13 @@
             name='showConfirmInfoBar',
             method_id_var_name='showConfirmInfoBar',
             java_class_name='',
-            params=[Param(datatype='int', name='nativeInfoBar'),
-                    Param(datatype='String', name='buttonOk'),
-                    Param(datatype='String', name='buttonCancel'),
-                    Param(datatype='String', name='title'),
-                    Param(datatype='Bitmap', name='icon')],
+            params=[
+                Param(datatype='int', name='nativeInfoBar'),
+                Param(datatype='String', name='buttonOk'),
+                Param(datatype='String', name='buttonCancel'),
+                Param(datatype='String', name='title'),
+                Param(datatype='Bitmap', name='icon')
+            ],
             env_call=('Object', ''),
             unchecked=False,
         ),
@@ -574,10 +604,12 @@
             name='showAutoLoginInfoBar',
             method_id_var_name='showAutoLoginInfoBar',
             java_class_name='',
-            params=[Param(datatype='int', name='nativeInfoBar'),
-                    Param(datatype='String', name='realm'),
-                    Param(datatype='String', name='account'),
-                    Param(datatype='String', name='args')],
+            params=[
+                Param(datatype='int', name='nativeInfoBar'),
+                Param(datatype='String', name='realm'),
+                Param(datatype='String', name='account'),
+                Param(datatype='String', name='args')
+            ],
             env_call=('Object', ''),
             unchecked=False,
         ),
@@ -599,10 +631,12 @@
             name='shouldShowAutoLogin',
             method_id_var_name='shouldShowAutoLogin',
             java_class_name='',
-            params=[Param(datatype='View', name='view'),
-                    Param(datatype='String', name='realm'),
-                    Param(datatype='String', name='account'),
-                    Param(datatype='String', name='args')],
+            params=[
+                Param(datatype='View', name='view'),
+                Param(datatype='String', name='realm'),
+                Param(datatype='String', name='account'),
+                Param(datatype='String', name='args')
+            ],
             env_call=('Boolean', ''),
             unchecked=False,
         ),
@@ -624,26 +658,28 @@
             name='activateHardwareAcceleration',
             method_id_var_name='activateHardwareAcceleration',
             java_class_name='',
-            params=[Param(datatype='boolean', name='activated'),
-                    Param(datatype='int', name='iPid'),
-                    Param(datatype='int', name='iType'),
-                    Param(datatype='int', name='iPrimaryID'),
-                    Param(datatype='int', name='iSecondaryID'),
-                   ],
+            params=[
+                Param(datatype='boolean', name='activated'),
+                Param(datatype='int', name='iPid'),
+                Param(datatype='int', name='iType'),
+                Param(datatype='int', name='iPrimaryID'),
+                Param(datatype='int', name='iSecondaryID'),
+            ],
             env_call=('Void', ''),
             unchecked=False,
         ),
         CalledByNative(
-          return_type='int',
-          system_class=False,
-          static=True,
-          name='updateStatus',
-          method_id_var_name='updateStatus',
-          java_class_name='',
-          params=[Param(annotations=['@Status'], datatype='int',
-                        name='status')],
-          env_call=('Integer', ''),
-          unchecked=False,
+            return_type='int',
+            system_class=False,
+            static=True,
+            name='updateStatus',
+            method_id_var_name='updateStatus',
+            java_class_name='',
+            params=[
+                Param(annotations=['@Status'], datatype='int', name='status')
+            ],
+            env_call=('Integer', ''),
+            unchecked=False,
         ),
         CalledByNative(
             return_type='void',
@@ -779,21 +815,24 @@
         ),
     ]
     self.AssertListEquals(golden_called_by_natives, called_by_natives)
-    h = jni_generator.InlHeaderFileGenerator(
-        '', 'org/chromium/TestJni', [], called_by_natives, [], jni_params,
-        TestOptions())
+    h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', [],
+                                             called_by_natives, [], jni_params,
+                                             TestOptions())
     self.AssertGoldenTextEquals(h.GetContent())
 
   def testCalledByNativeParseError(self):
     try:
       jni_params = jni_generator.JniParams('')
-      jni_generator.ExtractCalledByNatives(jni_params, """
+      jni_generator.ExtractCalledByNatives(
+          jni_params,
+          """
 @CalledByNative
 public static int foo(); // This one is fine
 
 @CalledByNative
 scooby doo
-""", always_mangle=False)
+""",
+          always_mangle=False)
       self.fail('Expected a ParseError')
     except jni_generator.ParseError as e:
       self.assertEqual(('@CalledByNative', 'scooby doo'), e.context_lines)
@@ -808,34 +847,35 @@
 
 import org.chromium.base.BuildInfo;
 """
-    self.assertEqual('org/chromium/content/browser/Foo',
-                     jni_generator.ExtractFullyQualifiedJavaClassName(
-                         'org/chromium/content/browser/Foo.java', contents))
-    self.assertEqual('org/chromium/content/browser/Foo',
-                     jni_generator.ExtractFullyQualifiedJavaClassName(
-                         'frameworks/Foo.java', contents))
+    self.assertEqual(
+        'org/chromium/content/browser/Foo',
+        jni_generator.ExtractFullyQualifiedJavaClassName(
+            'org/chromium/content/browser/Foo.java', contents))
+    self.assertEqual(
+        'org/chromium/content/browser/Foo',
+        jni_generator.ExtractFullyQualifiedJavaClassName(
+            'frameworks/Foo.java', contents))
     self.assertRaises(SyntaxError,
                       jni_generator.ExtractFullyQualifiedJavaClassName,
                       'com/foo/Bar', 'no PACKAGE line')
 
   def testMethodNameMangling(self):
     jni_params = jni_generator.JniParams('')
-    self.assertEqual('closeV',
+    self.assertEqual(
+        'closeV',
         jni_generator.GetMangledMethodName(jni_params, 'close', [], 'void'))
-    self.assertEqual('readI_AB_I_I',
-        jni_generator.GetMangledMethodName(jni_params, 'read',
-            [Param(name='p1',
-                   datatype='byte[]'),
-             Param(name='p2',
-                   datatype='int'),
-             Param(name='p3',
-                   datatype='int'),],
-             'int'))
-    self.assertEqual('openJIIS_JLS',
-        jni_generator.GetMangledMethodName(jni_params, 'open',
-            [Param(name='p1',
-                   datatype='java/lang/String'),],
-             'java/io/InputStream'))
+    self.assertEqual(
+        'readI_AB_I_I',
+        jni_generator.GetMangledMethodName(jni_params, 'read', [
+            Param(name='p1', datatype='byte[]'),
+            Param(name='p2', datatype='int'),
+            Param(name='p3', datatype='int'),
+        ], 'int'))
+    self.assertEqual(
+        'openJIIS_JLS',
+        jni_generator.GetMangledMethodName(jni_params, 'open', [
+            Param(name='p1', datatype='java/lang/String'),
+        ], 'java/io/InputStream'))
 
   def testMethodNameAlwaysMangle(self):
     test_data = """
@@ -851,9 +891,8 @@
     """
     jni_params = jni_generator.JniParams('org/chromium/Foo')
     jni_params.ExtractImportsAndInnerClasses(test_data)
-    called_by_natives = jni_generator.ExtractCalledByNatives(jni_params,
-                                                             test_data,
-                                                             always_mangle=True)
+    called_by_natives = jni_generator.ExtractCalledByNatives(
+        jni_params, test_data, always_mangle=True)
     self.assertEqual(1, len(called_by_natives))
     method = called_by_natives[0]
     self.assertEqual('methodzFOOB_FOOB', method.method_id_var_name)
@@ -868,8 +907,8 @@
       Signature: ()Ljava/lang/Class<*>;
 }
 """
-    jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'),
-                                                TestOptions())
+    jni_from_javap = jni_generator.JNIFromJavaP(
+        contents.split('\n'), TestOptions())
     self.assertEqual(2, len(jni_from_javap.called_by_natives))
     self.AssertGoldenTextEquals(jni_from_javap.GetContent())
 
@@ -895,12 +934,12 @@
 }
 """
 
-    jni_from_javap6 = jni_generator.JNIFromJavaP(content_javap6.split('\n'),
-                                                 TestOptions())
-    jni_from_javap7 = jni_generator.JNIFromJavaP(content_javap7.split('\n'),
-                                                 TestOptions())
-    jni_from_javap8 = jni_generator.JNIFromJavaP(content_javap8.split('\n'),
-                                                 TestOptions())
+    jni_from_javap6 = jni_generator.JNIFromJavaP(
+        content_javap6.split('\n'), TestOptions())
+    jni_from_javap7 = jni_generator.JNIFromJavaP(
+        content_javap7.split('\n'), TestOptions())
+    jni_from_javap8 = jni_generator.JNIFromJavaP(
+        content_javap8.split('\n'), TestOptions())
     self.assertTrue(jni_from_javap6.GetContent())
     self.assertTrue(jni_from_javap7.GetContent())
     self.assertTrue(jni_from_javap8.GetContent())
@@ -914,16 +953,16 @@
 
   def testFromJavaP(self):
     contents = self._ReadGoldenFile('testInputStream.javap')
-    jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'),
-                                                TestOptions())
+    jni_from_javap = jni_generator.JNIFromJavaP(
+        contents.split('\n'), TestOptions())
     self.assertEqual(10, len(jni_from_javap.called_by_natives))
     self.AssertGoldenTextEquals(jni_from_javap.GetContent())
 
   def testConstantsFromJavaP(self):
     for f in ['testMotionEvent.javap', 'testMotionEvent.javap7']:
       contents = self._ReadGoldenFile(f)
-      jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'),
-                                                  TestOptions())
+      jni_from_javap = jni_generator.JNIFromJavaP(
+          contents.split('\n'), TestOptions())
       self.assertEqual(86, len(jni_from_javap.called_by_natives))
       self.AssertGoldenTextEquals(jni_from_javap.GetContent())
 
@@ -932,7 +971,7 @@
     test_data = """
     /**
      * Invoked when the setup process is complete so we can disconnect from the
-     * native-side SyncSetupFlowHandler.
+     * private native void nativeSyncSetupFlowHandler();.
      */
     public void destroy() {
         Log.v(TAG, "Destroying native SyncSetupFlow");
@@ -946,6 +985,7 @@
     """
     jni_from_java = jni_generator.JNIFromJavaSource(
         test_data, 'foo/bar', TestOptions())
+    self.AssertGoldenTextEquals(jni_from_java.GetContent())
 
   def testRaisesOnNonJNIMethod(self):
     test_data = """
@@ -954,9 +994,8 @@
       }
     }
     """
-    self.assertRaises(SyntaxError,
-                      jni_generator.JNIFromJavaSource,
-                      test_data, 'foo/bar', TestOptions())
+    self.assertRaises(SyntaxError, jni_generator.JNIFromJavaSource, test_data,
+                      'foo/bar', TestOptions())
 
   def testJniSelfDocumentingExample(self):
     generated_text = self._CreateJniHeaderFromFile(
@@ -978,10 +1017,10 @@
                     'icankeepthisupallday/ReallyLongClassNamesAreAllTheRage'),
         TestOptions())
     jni_lines = jni_from_java.GetContent().split('\n')
-    line = next(line for line in jni_lines
-                if line.lstrip().startswith('#ifndef'))
-    self.assertTrue(len(line) > 80,
-                    ('Expected #ifndef line to be > 80 chars: ', line))
+    line = next(
+        line for line in jni_lines if line.lstrip().startswith('#ifndef'))
+    self.assertTrue(
+        len(line) > 80, ('Expected #ifndef line to be > 80 chars: ', line))
 
   def testImports(self):
     import_header = """
@@ -1027,16 +1066,14 @@
     jni_params.ExtractImportsAndInnerClasses(import_header)
     self.assertTrue('Lorg/chromium/content/common/ISandboxedProcessService' in
                     jni_params._imports)
-    self.assertTrue('Lorg/chromium/Bar/Zoo' in
-                    jni_params._imports)
-    self.assertTrue('Lorg/chromium/content/app/Foo$BookmarkNode' in
-                    jni_params._inner_classes)
+    self.assertTrue('Lorg/chromium/Bar/Zoo' in jni_params._imports)
+    self.assertTrue('Lorg/chromium/content/app/Foo$BookmarkNode' in jni_params.
+                    _inner_classes)
     self.assertTrue('Lorg/chromium/content/app/Foo$PasswordListObserver' in
                     jni_params._inner_classes)
     self.assertEqual('Lorg/chromium/content/app/ContentMain$Inner;',
                      jni_params.JavaToJni('ContentMain.Inner'))
-    self.assertRaises(SyntaxError,
-                      jni_params.JavaToJni, 'AnException')
+    self.assertRaises(SyntaxError, jni_params.JavaToJni, 'AnException')
 
   def testJniParamsJavaToJni(self):
     jni_params = jni_generator.JniParams('')
@@ -1055,46 +1092,48 @@
     jni_params.ExtractImportsAndInnerClasses(test_data)
     natives = jni_generator.ExtractNatives(test_data, test_options.ptr_type)
     golden_natives = [
-        NativeMethod(return_type='void', static=False, name='Destroy',
-                     params=[Param(datatype='long',
-                                   name='nativeChromeBrowserProvider')],
-                     java_class_name=None,
-                     type='method',
-                     p0_type='ChromeBrowserProvider',
-                     ptr_type=test_options.ptr_type),
+        NativeMethod(
+            return_type='void',
+            static=False,
+            name='Destroy',
+            params=[Param(datatype='long', name='nativeChromeBrowserProvider')],
+            java_class_name=None,
+            type='method',
+            p0_type='ChromeBrowserProvider',
+            ptr_type=test_options.ptr_type),
     ]
     self.AssertListEquals(golden_natives, natives)
-    h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni',
-                                             natives, [], [], jni_params,
-                                             test_options)
+    h = jni_generator.InlHeaderFileGenerator(
+        '', 'org/chromium/TestJni', natives, [], [], jni_params, test_options)
     self.AssertGoldenTextEquals(h.GetContent())
 
   def testMainDexAnnotation(self):
     mainDexEntries = [
-      '@MainDex public class Test {',
-      '@MainDex public class Test{',
-      """@MainDex
+        '@MainDex public class Test {',
+        '@MainDex public class Test{',
+        """@MainDex
          public class Test {
       """,
-      """@MainDex public class Test
+        """@MainDex public class Test
          {
       """,
-      '@MainDex /* This class is a test */ public class Test {',
-      '@MainDex public class Test implements java.io.Serializable {',
-      '@MainDex public class Test implements java.io.Serializable, Bidule {',
-      '@MainDex public class Test extends BaseTest {',
-      """@MainDex
+        '@MainDex /* This class is a test */ public class Test {',
+        '@MainDex public class Test implements java.io.Serializable {',
+        '@MainDex public class Test implements java.io.Serializable, Bidule {',
+        '@MainDex public class Test extends BaseTest {',
+        """@MainDex
          public class Test extends BaseTest implements Bidule {
       """,
-      """@MainDex
+        """@MainDex
          public class Test extends BaseTest implements Bidule, Machin, Chose {
       """,
-      """@MainDex
+        """@MainDex
          public class Test implements Testable<java.io.Serializable> {
       """,
-      '@MainDex public class Test implements Testable<java.io.Serializable> {',
-      '@a.B @MainDex @C public class Test extends Testable<Serializable> {',
-      """public class Test extends Testable<java.io.Serializable> {
+        '@MainDex public class Test implements Testable<java.io.Serializable> '
+        ' {',
+        '@a.B @MainDex @C public class Test extends Testable<Serializable> {',
+        """public class Test extends Testable<java.io.Serializable> {
          @MainDex void func() {}
       """,
     ]
@@ -1103,13 +1142,11 @@
 
   def testNoMainDexAnnotation(self):
     noMainDexEntries = [
-      'public class Test {',
-      '@NotMainDex public class Test {',
-      '// @MainDex public class Test {',
-      '/* @MainDex */ public class Test {',
-      'public class Test implements java.io.Serializable {',
-      '@MainDexNot public class Test {',
-      'public class Test extends BaseTest {'
+        'public class Test {', '@NotMainDex public class Test {',
+        '// @MainDex public class Test {', '/* @MainDex */ public class Test {',
+        'public class Test implements java.io.Serializable {',
+        '@MainDexNot public class Test {',
+        'public class Test extends BaseTest {'
     ]
     for entry in noMainDexEntries:
       self.assertEqual(False, IsMainDexJavaClass(entry))
@@ -1160,11 +1197,12 @@
         return format.getWidth();
     }
     """
+
     def willRaise():
-      jni_generator.JNIFromJavaSource(
-          test_data,
-          'org/chromium/media/VideoCaptureFactory',
-          TestOptions())
+      jni_generator.JNIFromJavaSource(test_data,
+                                      'org/chromium/media/VideoCaptureFactory',
+                                      TestOptions())
+
     self.assertRaises(SyntaxError, willRaise)
 
   def testSingleJNIAdditionalImport(self):
@@ -1246,8 +1284,9 @@
     }
     """
 
-    jni_from_java = jni_generator.JNIFromJavaSource(
-      test_data, 'org/chromium/foo/Foo', TestOptions())
+    jni_from_java = jni_generator.JNIFromJavaSource(test_data,
+                                                    'org/chromium/foo/Foo',
+                                                    TestOptions())
     self.AssertGoldenTextEquals(jni_from_java.GetContent())
 
 
@@ -1424,14 +1463,14 @@
 
     bad_spaced_test_data = """
     class SampleProxyJni{
-      @NativeMethods interface 
-      Natives 
-      
-      
-      { void     foo(); 
-      int              bar(int        
-      x,  int y); String    
-        foobar(String x, String y); 
+      @NativeMethods interface
+      Natives
+
+
+      { void     foo();
+      int              bar(int
+      x,  int y); String
+        foobar(String x, String y);
       }
 
     }
@@ -1563,9 +1602,7 @@
   options, _ = parser.parse_args(argv[1:])
 
   test_result = unittest.main(
-      argv=argv[0:1],
-      exit=False,
-      verbosity=(2 if options.verbose else 1))
+      argv=argv[0:1], exit=False, verbosity=(2 if options.verbose else 1))
 
   if test_result.result.wasSuccessful() and options.stamp:
     TouchStamp(options.stamp)
diff --git a/base/android/jni_generator/jni_refactorer.py b/base/android/jni_generator/jni_refactorer.py
index 0ef7830..de0da86 100755
--- a/base/android/jni_generator/jni_refactorer.py
+++ b/base/android/jni_generator/jni_refactorer.py
@@ -2,6 +2,7 @@
 # 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.
+
 """Tool for doing Java refactors over native methods.
 
 Converts
@@ -81,22 +82,22 @@
 _COMMENT_REGEX_STRING = r'(?:(?:(?:\/\*[^\/]*\*\/)+|(?:\/\/[^\n]*?\n))+\s*)*'
 
 _NATIVES_REGEX = re.compile(
-  r'(?P<comments>' + _COMMENT_REGEX_STRING + ')'
-  r'(?P<annotations>(@NativeClassQualifiedName'
-  r'\(\"(?P<native_class_name>.*?)\"\)\s+)?'
-  r'(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?)'
-  r'(?P<qualifiers>\w+\s\w+|\w+|\s+)\s+static\s*native '
-  r'(?P<return_type>\S*) '
-  r'(?P<name>native\w+)\((?P<params>.*?)\);\n', re.DOTALL)
+    r'(?P<comments>' + _COMMENT_REGEX_STRING + ')'
+    r'(?P<annotations>(@NativeClassQualifiedName'
+    r'\(\"(?P<native_class_name>.*?)\"\)\s+)?'
+    r'(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?)'
+    r'(?P<qualifiers>\w+\s\w+|\w+|\s+)\s+static\s*native '
+    r'(?P<return_type>\S*) '
+    r'(?P<name>native\w+)\((?P<params>.*?)\);\n', re.DOTALL)
 
 _NON_STATIC_NATIVES_REGEX = re.compile(
-  r'(?P<comments>' + _COMMENT_REGEX_STRING + ')'
-  r'(?P<annotations>(@NativeClassQualifiedName'
-  r'\(\"(?P<native_class_name>.*?)\"\)\s+)?'
-  r'(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?)'
-  r'(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native '
-  r'(?P<return_type>\S*) '
-  r'(?P<name>native\w+)\((?P<params>.*?)\);\n', re.DOTALL)
+    r'(?P<comments>' + _COMMENT_REGEX_STRING + ')'
+    r'(?P<annotations>(@NativeClassQualifiedName'
+    r'\(\"(?P<native_class_name>.*?)\"\)\s+)?'
+    r'(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?)'
+    r'(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native '
+    r'(?P<return_type>\S*) '
+    r'(?P<name>native\w+)\((?P<params>.*?)\);\n', re.DOTALL)
 
 JNI_IMPORT_STRING = 'import org.chromium.base.annotations.NativeMethods;'
 JCALLER_IMPORT_STRING = 'import org.chromium.base.annotations.JCaller;'
@@ -104,11 +105,12 @@
 
 PICKLE_LOCATION = './jni_ref_pickle'
 
+
 def build_method_declaration(return_type, name, params, annotations, comments):
   out = _JNI_METHOD_DECL.substitute({
-    'RETURN_TYPE': return_type,
-    'NAME': name,
-    'PARAMS': params
+      'RETURN_TYPE': return_type,
+      'NAME': name,
+      'PARAMS': params
   })
   if annotations:
     out = '\n' + annotations + out
@@ -163,8 +165,7 @@
   contents = add_chromium_import_to_java_file(contents, JCALLER_IMPORT_STRING)
 
   class_name = jni_generator.ExtractFullyQualifiedJavaClassName(
-    java_file_name, no_comment_content).split('/')[-1]
-
+      java_file_name, no_comment_content).split('/')[-1]
 
   replace_patterns = []
   should_add_comma = []
@@ -182,7 +183,7 @@
       qual_end = match.end('qualifiers') + insertion_offset
       insert_str = ' static '
       new_contents = new_contents[:qual_end] + insert_str + new_contents[
-                                                            qual_end:]
+          qual_end:]
       insertion_offset += len(insert_str)
 
       # Insert an object param.
@@ -211,10 +212,10 @@
   for i, r in enumerate(replace_patterns):
     if should_add_comma[i]:
       new_contents = re.sub(
-        r, '\g<1>%s.this, ' % class_name, new_contents, flags=re.MULTILINE)
+          r, '\g<1>%s.this, ' % class_name, new_contents, flags=re.MULTILINE)
     else:
       new_contents = re.sub(
-        r, '\g<1>%s.this' % class_name, new_contents, flags=re.MULTILINE)
+          r, '\g<1>%s.this' % class_name, new_contents, flags=re.MULTILINE)
 
   if dry:
     print(new_contents)
@@ -283,11 +284,11 @@
     comments = n_dict['comments']
     annotations = n_dict['annotations']
     methods.append(
-      build_method_declaration(n.return_type, new_name, params, annotations,
-                               comments))
+        build_method_declaration(n.return_type, new_name, params, annotations,
+                                 comments))
 
   fully_qualified_class = jni_generator.ExtractFullyQualifiedJavaClassName(
-    java_file_name, contents)
+      java_file_name, contents)
   class_name = fully_qualified_class.split('/')[-1]
   jni_class_name = class_name + 'Jni'
 
@@ -310,15 +311,15 @@
 
   # Build and insert the @NativeMethods interface.
   interface = _JNI_INTERFACE_TEMPLATES.substitute({
-    'INTERFACE_NAME': 'Natives',
-    'METHODS': ''.join(methods)
+      'INTERFACE_NAME': 'Natives',
+      'METHODS': ''.join(methods)
   })
 
   # Insert the interface at the bottom of the top level class.
   # Most of the time this will be before the last }.
   insertion_point = contents.rfind('}')
   contents = contents[:insertion_point] + '\n' + interface + contents[
-                                                             insertion_point:]
+      insertion_point:]
 
   if not dry:
     with open(java_file_name, 'w') as f:
@@ -334,41 +335,41 @@
   mutually_ex_group = arg_parser.add_mutually_exclusive_group()
 
   mutually_ex_group.add_argument(
-    '-R',
-    '--recursive',
-    action='store_true',
-    help='Run recursively over all java files '
-         'descendants of the current directory.',
-    default=False)
+      '-R',
+      '--recursive',
+      action='store_true',
+      help='Run recursively over all java files '
+      'descendants of the current directory.',
+      default=False)
   mutually_ex_group.add_argument(
-    '--read_cache',
-    help='Reads paths to refactor from pickled file %s.' % PICKLE_LOCATION,
-    action='store_true',
-    default=False)
+      '--read_cache',
+      help='Reads paths to refactor from pickled file %s.' % PICKLE_LOCATION,
+      action='store_true',
+      default=False)
   mutually_ex_group.add_argument(
-    '--source', help='Path to refactor single source file.', default=None)
+      '--source', help='Path to refactor single source file.', default=None)
 
   arg_parser.add_argument(
-    '--cache',
-    action='store_true',
-    help='Finds all java files with native functions recursively from '
-         'the current directory, then pickles and saves them to %s and then'
-         'exits.' % PICKLE_LOCATION,
-    default=False)
+      '--cache',
+      action='store_true',
+      help='Finds all java files with native functions recursively from '
+      'the current directory, then pickles and saves them to %s and then'
+      'exits.' % PICKLE_LOCATION,
+      default=False)
   arg_parser.add_argument(
-    '--dry_run',
-    default=False,
-    action='store_true',
-    help='Print refactor output to console instead '
-         'of replacing the contents of files.')
+      '--dry_run',
+      default=False,
+      action='store_true',
+      help='Print refactor output to console instead '
+      'of replacing the contents of files.')
   arg_parser.add_argument(
-    '--nonstatic',
-    default=False,
-    action='store_true',
-    help='If true converts native nonstatic methods to static methods'
-         ' instead of converting static methods to new jni.')
+      '--nonstatic',
+      default=False,
+      action='store_true',
+      help='If true converts native nonstatic methods to static methods'
+      ' instead of converting static methods to new jni.')
   arg_parser.add_argument(
-    '--verbose', default=False, action='store_true', help='')
+      '--verbose', default=False, action='store_true', help='')
 
   args = arg_parser.parse_args()
 
@@ -383,19 +384,21 @@
       print('Found %s java paths.' % len(java_file_paths))
   elif args.recursive:
     ignored_paths = [
-      'third_party', 'src/out', 'out/Debug', 'library_loader', '.cipd',
-      'jni_generator', 'media', 'accessibility', '/vr', 'website', 'gcm_driver',
-      'preferences'
+        'third_party', 'src/out', 'out/Debug', 'library_loader', '.cipd',
+        'jni_generator', 'media', 'accessibility', '/vr', 'website',
+        'gcm_driver', 'preferences'
     ]
 
     for root, dirs, files in os.walk(os.path.abspath('.')):
+
       def getPaths():
         for ignored_path in ignored_paths:
           if ignored_path in root:
             return
 
           java_file_paths.extend(
-            ['%s/%s' % (root, f) for f in files if f.endswith('.java')])
+              ['%s/%s' % (root, f) for f in files if f.endswith('.java')])
+
       getPaths()
 
   else:
@@ -405,8 +408,7 @@
 
   if args.cache:
     with open(PICKLE_LOCATION, 'w') as file:
-      pickle.dump(
-        filter_files_with_natives(java_file_paths), file)
+      pickle.dump(filter_files_with_natives(java_file_paths), file)
       print('Java files with proxy natives written to ' + PICKLE_LOCATION)
 
   i = 1
diff --git a/base/android/jni_generator/jni_registration_generator.py b/base/android/jni_generator/jni_registration_generator.py
index de5cbb4..0cc75a1 100755
--- a/base/android/jni_generator/jni_registration_generator.py
+++ b/base/android/jni_generator/jni_registration_generator.py
@@ -20,7 +20,6 @@
 import jni_generator
 from util import build_utils
 
-
 # All but FULL_CLASS_NAME, which is used only for sorting.
 MERGEABLE_KEYS = [
     'CLASS_PATH_DECLARATIONS',
@@ -34,6 +33,7 @@
     'REGISTER_NON_MAIN_DEX_NATIVES',
 ]
 
+
 def _Generate(java_file_paths,
               srcjar_path,
               proxy_opts,
@@ -126,7 +126,8 @@
 JNI_REGISTRATION_EXPORT bool ${REGISTRATION_NAME}(JNIEnv* env) {
   const int number_of_methods = base::size(kMethods_${ESCAPED_PROXY_CLASS});
 
-  base::android::ScopedJavaLocalRef<jclass> native_clazz = base::android::GetClass(env, "${PROXY_CLASS}");
+  base::android::ScopedJavaLocalRef<jclass> native_clazz =
+      base::android::GetClass(env, "${PROXY_CLASS}");
   if (env->RegisterNatives(
       native_clazz.obj(),
       kMethods_${ESCAPED_PROXY_CLASS},
@@ -150,15 +151,15 @@
 
   sub_dict = {
       'ESCAPED_PROXY_CLASS':
-          jni_generator.EscapeClassName(
-              jni_generator.ProxyHelpers.GetQualifiedClass(use_hash)),
+      jni_generator.EscapeClassName(
+          jni_generator.ProxyHelpers.GetQualifiedClass(use_hash)),
       'PROXY_CLASS':
-          jni_generator.ProxyHelpers.GetQualifiedClass(use_hash),
+      jni_generator.ProxyHelpers.GetQualifiedClass(use_hash),
       'KMETHODS':
-          registration_dict['PROXY_NATIVE_METHOD_ARRAY'],
+      registration_dict['PROXY_NATIVE_METHOD_ARRAY'],
       'REGISTRATION_NAME':
-          jni_generator.GetRegistrationFunctionName(
-              jni_generator.ProxyHelpers.GetQualifiedClass(use_hash)),
+      jni_generator.GetRegistrationFunctionName(
+          jni_generator.ProxyHelpers.GetQualifiedClass(use_hash)),
   }
 
   if registration_dict['PROXY_NATIVE_METHOD_ARRAY']:
@@ -205,16 +206,16 @@
 
   return template.substitute({
       'TESTING_ENABLED':
-          str(proxy_opts.enable_mocks).lower(),
+      str(proxy_opts.enable_mocks).lower(),
       'REQUIRE_MOCK':
-          str(proxy_opts.require_mocks).lower(),
+      str(proxy_opts.require_mocks).lower(),
       'CLASS_NAME':
-          jni_generator.ProxyHelpers.GetClass(proxy_opts.use_hash),
+      jni_generator.ProxyHelpers.GetClass(proxy_opts.use_hash),
       'PACKAGE':
-          jni_generator.ProxyHelpers.GetPackage(proxy_opts.use_hash).replace(
-              '/', '.'),
+      jni_generator.ProxyHelpers.GetPackage(proxy_opts.use_hash).replace(
+          '/', '.'),
       'SIGNATURES':
-          registration_dict['PROXY_NATIVE_SIGNATURES']
+      registration_dict['PROXY_NATIVE_SIGNATURES']
   })
 
 
@@ -316,7 +317,8 @@
 
   def _AddClassPathDeclarations(self):
     classes = self.helper.GetUniqueClasses(self.natives)
-    self._SetDictValue('CLASS_PATH_DECLARATIONS',
+    self._SetDictValue(
+        'CLASS_PATH_DECLARATIONS',
         self.helper.GetClassPathLines(classes, declare_only=True))
 
   def _AddForwardDeclaration(self):
@@ -349,8 +351,7 @@
 """)
     value = {
         'REGISTER_NAME':
-            jni_generator.GetRegistrationFunctionName(
-                self.fully_qualified_class)
+        jni_generator.GetRegistrationFunctionName(self.fully_qualified_class)
     }
     register_body = template.substitute(value)
     if self.main_dex:
@@ -377,14 +378,14 @@
       close_namespace = '\n'.join(all_namespaces) + '\n\n'
 
     body = self._SubstituteNativeMethods(template)
-    self._SetDictValue('JNI_NATIVE_METHOD_ARRAY',
-                       ''.join((open_namespace, body, close_namespace)))
+    self._SetDictValue('JNI_NATIVE_METHOD_ARRAY', ''.join((open_namespace, body,
+                                                           close_namespace)))
 
   def _GetKMethodsString(self, clazz):
     ret = []
     for native in self.non_proxy_natives:
-      if (native.java_class_name == clazz or
-          (not native.java_class_name and clazz == self.class_name)):
+      if (native.java_class_name == clazz
+          or (not native.java_class_name and clazz == self.class_name)):
         ret += [self._GetKMethodArrayEntry(native)]
     return '\n'.join(ret)
 
@@ -399,11 +400,11 @@
       name = native.proxy_name
     values = {
         'NAME':
-            name,
+        name,
         'JNI_SIGNATURE':
-            self.jni_params.Signature(native.params, native.return_type),
+        self.jni_params.Signature(native.params, native.return_type),
         'STUB_NAME':
-            self.helper.GetStubName(native)
+        self.helper.GetStubName(native)
     }
     return template.substitute(values)
 
@@ -473,9 +474,10 @@
 
 """)
     values = {
-      'REGISTER_NAME': jni_generator.GetRegistrationFunctionName(
-          self.fully_qualified_class),
-      'NATIVES': natives
+        'REGISTER_NAME':
+        jni_generator.GetRegistrationFunctionName(self.fully_qualified_class),
+        'NATIVES':
+        natives
     }
     self._SetDictValue('JNI_NATIVE_METHOD', template.substitute(values))
 
@@ -508,11 +510,11 @@
 
   return signature_template.substitute({
       'RETURN_TYPE':
-          proxy_native.return_type,
+      proxy_native.return_type,
       'NAME':
-          proxy_native.proxy_name,
+      proxy_native.proxy_name,
       'PARAMS':
-          jni_generator.JniParams.MakeProxyParamSignature(proxy_native.params)
+      jni_generator.JniParams.MakeProxyParamSignature(proxy_native.params)
   })
 
 
@@ -547,10 +549,11 @@
       default=[],
       help='A list of Java files which should be ignored '
       'by the parser.')
-  arg_parser.add_argument('--namespace',
-                          default='',
-                          help='Namespace to wrap the registration functions '
-                          'into.')
+  arg_parser.add_argument(
+      '--namespace',
+      default='',
+      help='Namespace to wrap the registration functions '
+      'into.')
   # TODO(crbug.com/898261) hook these flags up to the build config to enable
   # mocking in instrumentation tests
   arg_parser.add_argument(
diff --git a/base/android/library_loader/library_loader_hooks.cc b/base/android/library_loader/library_loader_hooks.cc
index 4fae2e3d5..9a7414f 100644
--- a/base/android/library_loader/library_loader_hooks.cc
+++ b/base/android/library_loader/library_loader_hooks.cc
@@ -4,6 +4,8 @@
 
 #include "base/android/library_loader/library_loader_hooks.h"
 
+#include <string>
+
 #include "base/android/jni_string.h"
 #include "base/android/library_loader/anchor_functions_buildflags.h"
 #include "base/android/library_loader/library_load_from_apk_status_codes.h"
@@ -250,30 +252,6 @@
   }
 }
 
-static void JNI_LibraryLoader_ForkAndPrefetchNativeLibrary(JNIEnv* env) {
-#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
-  return NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary(
-      IsUsingOrderfileOptimization());
-#endif
-}
-
-static jint JNI_LibraryLoader_PercentageOfResidentNativeLibraryCode(
-    JNIEnv* env) {
-#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
-  return NativeLibraryPrefetcher::PercentageOfResidentNativeLibraryCode();
-#else
-  return -1;
-#endif
-}
-
-static void JNI_LibraryLoader_PeriodicallyCollectResidency(JNIEnv* env) {
-#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
-  NativeLibraryPrefetcher::PeriodicallyCollectResidency();
-#else
-  LOG(WARNING) << "Collecting residency is not supported.";
-#endif
-}
-
 void SetVersionNumber(const char* version_number) {
   g_library_version_number = strdup(version_number);
 }
diff --git a/base/android/library_loader/library_prefetcher_hooks.cc b/base/android/library_loader/library_prefetcher_hooks.cc
new file mode 100644
index 0000000..43e3aea
--- /dev/null
+++ b/base/android/library_loader/library_prefetcher_hooks.cc
@@ -0,0 +1,39 @@
+// 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 "base/android/library_loader/anchor_functions_buildflags.h"
+#include "base/android/library_loader/library_loader_hooks.h"
+#include "base/android/library_loader/library_prefetcher.h"
+#include "base/logging.h"
+#include "jni/LibraryPrefetcher_jni.h"
+
+namespace base {
+namespace android {
+
+static void JNI_LibraryPrefetcher_ForkAndPrefetchNativeLibrary(JNIEnv* env) {
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+  return NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary(
+      IsUsingOrderfileOptimization());
+#endif
+}
+
+static jint JNI_LibraryPrefetcher_PercentageOfResidentNativeLibraryCode(
+    JNIEnv* env) {
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+  return NativeLibraryPrefetcher::PercentageOfResidentNativeLibraryCode();
+#else
+  return -1;
+#endif
+}
+
+static void JNI_LibraryPrefetcher_PeriodicallyCollectResidency(JNIEnv* env) {
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+  NativeLibraryPrefetcher::PeriodicallyCollectResidency();
+#else
+  LOG(WARNING) << "Collecting residency is not supported.";
+#endif
+}
+
+}  // namespace android
+}  // namespace base
diff --git a/base/bind_unittest.cc b/base/bind_unittest.cc
index af5f13b..4dd03e5 100644
--- a/base/bind_unittest.cc
+++ b/base/bind_unittest.cc
@@ -1342,11 +1342,24 @@
 }
 
 TEST_F(BindTest, CapturingLambdaForTesting) {
+  // Test copyable lambdas.
   int x = 6;
   EXPECT_EQ(42, BindLambdaForTesting([=](int y) { return x * y; }).Run(7));
-
+  EXPECT_EQ(42,
+            BindLambdaForTesting([=](int y) mutable { return x *= y; }).Run(7));
   auto f = [x](std::unique_ptr<int> y) { return x * *y; };
   EXPECT_EQ(42, BindLambdaForTesting(f).Run(std::make_unique<int>(7)));
+
+  // Test move-only lambdas.
+  auto y = std::make_unique<int>(7);
+  auto g = [y = std::move(y)](int& x) mutable {
+    return x * *std::exchange(y, nullptr);
+  };
+  EXPECT_EQ(42, BindLambdaForTesting(std::move(g)).Run(x));
+
+  y = std::make_unique<int>(7);
+  auto h = [x, y = std::move(y)] { return x * *y; };
+  EXPECT_EQ(42, BindLambdaForTesting(std::move(h)).Run());
 }
 
 TEST_F(BindTest, Cancellation) {
diff --git a/base/bind_unittest.nc b/base/bind_unittest.nc
index 9b32d6c..349d38b 100644
--- a/base/bind_unittest.nc
+++ b/base/bind_unittest.nc
@@ -313,6 +313,20 @@
   Bind(NonEmptyFunctor());
 }
 
+#elif defined(NCTEST_DISALLOW_BINDLAMBDAFORTESTING_LVALUE_MUTABLE_LAMBDA)  // [r"BindLambdaForTesting requires non-const rvalue for mutable lambda binding\. I\.e\.: base::BindLambdaForTesting\(std::move\(lambda\)\)."]
+void WontCompile() {
+  int foo = 42;
+  auto mutable_lambda = [&]() mutable {};
+  BindLambdaForTesting(mutable_lambda);
+}
+
+#elif defined(NCTEST_DISALLOW_BINDLAMBDAFORTESTING_RVALUE_CONST_MUTABLE_LAMBDA)  // [r"BindLambdaForTesting requires non-const rvalue for mutable lambda binding\. I\.e\.: base::BindLambdaForTesting\(std::move\(lambda\)\)."]
+
+void WontCompile() {
+  int foo = 42;
+  const auto mutable_lambda = [&]() mutable {};
+  BindLambdaForTesting(std::move(mutable_lambda));
+}
 #endif
 
 }  // namespace base
diff --git a/base/callback_internal.h b/base/callback_internal.h
index 7e49a8b..390461c 100644
--- a/base/callback_internal.h
+++ b/base/callback_internal.h
@@ -19,6 +19,7 @@
 
 namespace internal {
 
+class FinallyExecutorCommon;
 class ThenAndCatchExecutorCommon;
 class BindStateBase;
 
@@ -136,6 +137,7 @@
   void Reset();
 
  protected:
+  friend class FinallyExecutorCommon;
   friend class ThenAndCatchExecutorCommon;
 
   using InvokeFuncStorage = BindStateBase::InvokeFuncStorage;
diff --git a/base/hash/hash.cc b/base/hash/hash.cc
index 988daea..778aa6f 100644
--- a/base/hash/hash.cc
+++ b/base/hash/hash.cc
@@ -4,6 +4,9 @@
 
 #include "base/hash/hash.h"
 
+#include "base/third_party/cityhash/city.h"
+#include "build/build_config.h"
+
 // Definition in base/third_party/superfasthash/superfasthash.c. (Third-party
 // code did not come with its own header file, so declaring the function here.)
 // Note: This algorithm is also in Blink under Source/wtf/StringHasher.h.
@@ -11,6 +14,26 @@
 
 namespace base {
 
+size_t FastHash(base::span<const uint8_t> data) {
+  // We use the updated CityHash within our namespace (not the deprecated
+  // version from third_party/smhasher).
+#if defined(ARCH_CPU_64_BITS)
+  return base::internal::cityhash_v111::CityHash64(
+      reinterpret_cast<const char*>(data.data()), data.size());
+#else
+  return base::internal::cityhash_v111::CityHash32(
+      reinterpret_cast<const char*>(data.data()), data.size());
+#endif
+}
+
+size_t FastHash(const std::string& str) {
+#if defined(ARCH_CPU_64_BITS)
+  return base::internal::cityhash_v111::CityHash64(str.data(), str.size());
+#else
+  return base::internal::cityhash_v111::CityHash32(str.data(), str.size());
+#endif
+}
+
 uint32_t Hash(const void* data, size_t length) {
   // Currently our in-memory hash is the same as the persistent hash. The
   // split between in-memory and persistent hash functions is maintained to
diff --git a/base/hash/hash.h b/base/hash/hash.h
index 4bf6829..0f98e4f 100644
--- a/base/hash/hash.h
+++ b/base/hash/hash.h
@@ -13,20 +13,32 @@
 #include <utility>
 
 #include "base/base_export.h"
+#include "base/containers/span.h"
 #include "base/logging.h"
 #include "base/strings/string16.h"
 
 namespace base {
 
-// Computes a hash of a memory buffer. This hash function is subject to change
-// in the future, so use only for temporary in-memory structures. If you need
-// to persist a change on disk or between computers, use PersistentHash().
-//
-// WARNING: This hash function should not be used for any cryptographic purpose.
+// WARNING: This hash functions should not be used for any cryptographic
+// purpose.
+
+// Deprecated: Computes a hash of a memory buffer, use FastHash() instead.
+// If you need to persist a change on disk or between computers, use
+// PersistentHash().
+// TODO(cavalcantii): Migrate client code to new hash function.
 BASE_EXPORT uint32_t Hash(const void* data, size_t length);
 BASE_EXPORT uint32_t Hash(const std::string& str);
 BASE_EXPORT uint32_t Hash(const string16& str);
 
+// Really *fast* and high quality hash.
+// Recommended hash function for general use, we pick the best performant
+// hash for each build target.
+// It is prone to be updated whenever a newer/faster hash function is
+// publicly available.
+// May changed without warning, do not expect stability of outputs.
+BASE_EXPORT size_t FastHash(base::span<const uint8_t> data);
+BASE_EXPORT size_t FastHash(const std::string& str);
+
 // Computes a hash of a memory buffer. This hash function must not change so
 // that code can use the hashed values for persistent storage purposes or
 // sending across the network. If a new persistent hash function is desired, a
diff --git a/base/metrics/bucket_ranges.cc b/base/metrics/bucket_ranges.cc
index 2723c3e..a3473bb 100644
--- a/base/metrics/bucket_ranges.cc
+++ b/base/metrics/bucket_ranges.cc
@@ -7,86 +7,10 @@
 #include <cmath>
 
 #include "base/logging.h"
+#include "base/metrics/crc32.h"
 
 namespace base {
 
-// Static table of checksums for all possible 8 bit bytes.
-const uint32_t kCrcTable[256] = {
-    0x0,         0x77073096L, 0xee0e612cL, 0x990951baL, 0x76dc419L,
-    0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0xedb8832L,  0x79dcb8a4L,
-    0xe0d5e91eL, 0x97d2d988L, 0x9b64c2bL,  0x7eb17cbdL, 0xe7b82d07L,
-    0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
-    0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
-    0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
-    0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
-    0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
-    0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
-    0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
-    0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
-    0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
-    0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
-    0x1db7106L,  0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x6b6b51fL,
-    0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0xf00f934L,  0x9609a88eL,
-    0xe10e9818L, 0x7f6a0dbbL, 0x86d3d2dL,  0x91646c97L, 0xe6635c01L,
-    0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
-    0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
-    0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
-    0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
-    0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
-    0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
-    0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
-    0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
-    0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
-    0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
-    0x3b6e20cL,  0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x4db2615L,
-    0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0xd6d6a3eL,  0x7a6a5aa8L,
-    0xe40ecf0bL, 0x9309ff9dL, 0xa00ae27L,  0x7d079eb1L, 0xf00f9344L,
-    0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
-    0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
-    0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
-    0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
-    0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
-    0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
-    0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
-    0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
-    0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
-    0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
-    0x26d930aL,  0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x5005713L,
-    0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0xcb61b38L,  0x92d28e9bL,
-    0xe5d5be0dL, 0x7cdcefb7L, 0xbdbdf21L,  0x86d3d2d4L, 0xf1d4e242L,
-    0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
-    0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
-    0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
-    0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
-    0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
-    0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
-    0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
-    0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
-    0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
-    0x2d02ef8dL,
-};
-
-// We generate the CRC-32 using the low order bits to select whether to XOR in
-// the reversed polynomial 0xedb88320L.  This is nice and simple, and allows us
-// to keep the quotient in a uint32_t.  Since we're not concerned about the
-// nature of corruptions (i.e., we don't care about bit sequencing, since we are
-// handling memory changes, which are more grotesque) so we don't bother to get
-// the CRC correct for big-endian vs little-ending calculations.  All we need is
-// a nice hash, that tends to depend on all the bits of the sample, with very
-// little chance of changes in one place impacting changes in another place.
-// Temporary non-static for https://crbug.com/836238
-/*static*/ uint32_t Crc32(uint32_t sum, HistogramBase::Sample value) {
-  union {
-    HistogramBase::Sample range;
-    unsigned char bytes[sizeof(HistogramBase::Sample)];
-  } converter;
-  converter.range = value;
-  for (size_t i = 0; i < sizeof(converter); ++i) {
-    sum = kCrcTable[(sum & 0xff) ^ converter.bytes[i]] ^ (sum >> 8);
-  }
-  return sum;
-}
-
 BucketRanges::BucketRanges(size_t num_ranges)
     : ranges_(num_ranges, 0),
       checksum_(0) {}
@@ -94,12 +18,16 @@
 BucketRanges::~BucketRanges() = default;
 
 uint32_t BucketRanges::CalculateChecksum() const {
-  // Seed checksum.
-  uint32_t checksum = static_cast<uint32_t>(ranges_.size());
+  // Crc of empty ranges_ happens to be 0. This early exit prevents trying to
+  // take the address of ranges_[0] which will fail for an empty vector even
+  // if that address is never used.
+  const size_t ranges_size = ranges_.size();
+  if (ranges_size == 0)
+    return 0;
 
-  for (size_t index = 0; index < ranges_.size(); ++index)
-    checksum = Crc32(checksum, ranges_[index]);
-  return checksum;
+  // Checksum is seeded with the ranges "size".
+  return Crc32(static_cast<uint32_t>(ranges_size), &ranges_[0],
+               sizeof(ranges_[0]) * ranges_size);
 }
 
 bool BucketRanges::HasValidChecksum() const {
diff --git a/base/metrics/bucket_ranges.h b/base/metrics/bucket_ranges.h
index 476d2df..f01c3d78 100644
--- a/base/metrics/bucket_ranges.h
+++ b/base/metrics/bucket_ranges.h
@@ -96,11 +96,6 @@
   DISALLOW_COPY_AND_ASSIGN(BucketRanges);
 };
 
-//////////////////////////////////////////////////////////////////////////////
-// Expose only for test.
-BASE_EXPORT extern const uint32_t kCrcTable[256];
-uint32_t Crc32(uint32_t sum, HistogramBase::Sample value);
-
 }  // namespace base
 
 #endif  // BASE_METRICS_BUCKET_RANGES_H_
diff --git a/base/metrics/bucket_ranges_unittest.cc b/base/metrics/bucket_ranges_unittest.cc
index 481054c..cabc170 100644
--- a/base/metrics/bucket_ranges_unittest.cc
+++ b/base/metrics/bucket_ranges_unittest.cc
@@ -74,21 +74,5 @@
   EXPECT_TRUE(ranges.HasValidChecksum());
 }
 
-// Table was generated similarly to sample code for CRC-32 given on:
-// http://www.w3.org/TR/PNG/#D-CRCAppendix.
-TEST(BucketRangesTest, Crc32TableTest) {
-  for (int i = 0; i < 256; ++i) {
-    uint32_t checksum = i;
-    for (int j = 0; j < 8; ++j) {
-      const uint32_t kReversedPolynomial = 0xedb88320L;
-      if (checksum & 1)
-        checksum = kReversedPolynomial ^ (checksum >> 1);
-      else
-        checksum >>= 1;
-    }
-    EXPECT_EQ(kCrcTable[i], checksum);
-  }
-}
-
 }  // namespace
 }  // namespace base
diff --git a/base/metrics/crc32.cc b/base/metrics/crc32.cc
new file mode 100644
index 0000000..8aab9fa
--- /dev/null
+++ b/base/metrics/crc32.cc
@@ -0,0 +1,81 @@
+// 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 "base/metrics/crc32.h"
+
+namespace base {
+
+// Static table of checksums for all possible 8 bit bytes.
+const uint32_t kCrcTable[256] = {
+    0x0,         0x77073096L, 0xee0e612cL, 0x990951baL, 0x76dc419L,
+    0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0xedb8832L,  0x79dcb8a4L,
+    0xe0d5e91eL, 0x97d2d988L, 0x9b64c2bL,  0x7eb17cbdL, 0xe7b82d07L,
+    0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
+    0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
+    0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
+    0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
+    0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
+    0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
+    0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
+    0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
+    0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
+    0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
+    0x1db7106L,  0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x6b6b51fL,
+    0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0xf00f934L,  0x9609a88eL,
+    0xe10e9818L, 0x7f6a0dbbL, 0x86d3d2dL,  0x91646c97L, 0xe6635c01L,
+    0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
+    0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
+    0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
+    0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
+    0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
+    0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
+    0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
+    0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
+    0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
+    0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
+    0x3b6e20cL,  0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x4db2615L,
+    0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0xd6d6a3eL,  0x7a6a5aa8L,
+    0xe40ecf0bL, 0x9309ff9dL, 0xa00ae27L,  0x7d079eb1L, 0xf00f9344L,
+    0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
+    0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
+    0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
+    0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
+    0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
+    0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
+    0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
+    0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
+    0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
+    0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
+    0x26d930aL,  0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x5005713L,
+    0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0xcb61b38L,  0x92d28e9bL,
+    0xe5d5be0dL, 0x7cdcefb7L, 0xbdbdf21L,  0x86d3d2d4L, 0xf1d4e242L,
+    0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
+    0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
+    0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
+    0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
+    0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
+    0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
+    0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
+    0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
+    0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
+    0x2d02ef8dL,
+};
+
+// We generate the CRC-32 using the low order bits to select whether to XOR in
+// the reversed polynomial 0xEDB88320.  This is nice and simple, and allows us
+// to keep the quotient in a uint32_t.  Since we're not concerned about the
+// nature of corruptions (i.e., we don't care about bit sequencing, since we are
+// handling memory changes, which are more grotesque) so we don't bother to get
+// the CRC correct for big-endian vs little-ending calculations.  All we need is
+// a nice hash, that tends to depend on all the bits of the sample, with very
+// little chance of changes in one place impacting changes in another place.
+uint32_t Crc32(uint32_t sum, const void* data, size_t size) {
+  const unsigned char* bytes = reinterpret_cast<const unsigned char*>(data);
+  for (size_t i = 0; i < size; ++i) {
+    sum = kCrcTable[(sum & 0x000000FF) ^ bytes[i]] ^ (sum >> 8);
+  }
+  return sum;
+}
+
+}  // namespace base
diff --git a/base/metrics/crc32.h b/base/metrics/crc32.h
new file mode 100644
index 0000000..736d492
--- /dev/null
+++ b/base/metrics/crc32.h
@@ -0,0 +1,24 @@
+// 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 BASE_METRICS_CRC32_H_
+#define BASE_METRICS_CRC32_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/base_export.h"
+
+namespace base {
+
+BASE_EXPORT extern const uint32_t kCrcTable[256];
+
+// This provides a simple, fast CRC-32 calculation that can be used for checking
+// the integrity of data.  It is not a "secure" calculation!  |sum| can start
+// with any seed or be used to continue an operation began with previous data.
+BASE_EXPORT uint32_t Crc32(uint32_t sum, const void* data, size_t size);
+
+}  // namespace base
+
+#endif  // BASE_METRICS_CRC32_H_
diff --git a/base/metrics/crc32_unittest.cc b/base/metrics/crc32_unittest.cc
new file mode 100644
index 0000000..a5e5d29
--- /dev/null
+++ b/base/metrics/crc32_unittest.cc
@@ -0,0 +1,34 @@
+// 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 "base/metrics/crc32.h"
+
+#include <stdint.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+// Table was generated similarly to sample code for CRC-32 given on:
+// http://www.w3.org/TR/PNG/#D-CRCAppendix.
+TEST(Crc32Test, TableTest) {
+  for (int i = 0; i < 256; ++i) {
+    uint32_t checksum = i;
+    for (int j = 0; j < 8; ++j) {
+      const uint32_t kReversedPolynomial = 0xEDB88320L;
+      if (checksum & 1)
+        checksum = kReversedPolynomial ^ (checksum >> 1);
+      else
+        checksum >>= 1;
+    }
+    EXPECT_EQ(kCrcTable[i], checksum);
+  }
+}
+
+// A CRC of nothing should always be zero.
+TEST(Crc32Test, ZeroTest) {
+  EXPECT_EQ(0U, Crc32(0, nullptr, 0));
+}
+
+}  // namespace base
diff --git a/base/task/promise/abstract_promise_unittest.cc b/base/task/promise/abstract_promise_unittest.cc
index 77b84bf..964be98 100644
--- a/base/task/promise/abstract_promise_unittest.cc
+++ b/base/task/promise/abstract_promise_unittest.cc
@@ -2073,9 +2073,6 @@
 
   p1->OnResolved();
 
-  EXPECT_FALSE(p2->IsResolved());
-  EXPECT_FALSE(p3->IsResolved());
-  EXPECT_FALSE(p4->IsResolved());
   EXPECT_FALSE(p5->IsResolved());
   run_loop.Run();
   EXPECT_TRUE(p2->IsResolved());
diff --git a/base/task/promise/finally_executor.cc b/base/task/promise/finally_executor.cc
new file mode 100644
index 0000000..27cc023
--- /dev/null
+++ b/base/task/promise/finally_executor.cc
@@ -0,0 +1,20 @@
+// 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 "base/task/promise/finally_executor.h"
+
+namespace base {
+namespace internal {
+
+FinallyExecutorCommon::FinallyExecutorCommon(internal::CallbackBase&& callback)
+    : callback_(std::move(callback)) {}
+
+FinallyExecutorCommon::~FinallyExecutorCommon() = default;
+
+bool FinallyExecutorCommon::IsCancelled() const {
+  return callback_.IsCancelled();
+}
+
+}  // namespace internal
+}  // namespace base
diff --git a/base/task/promise/finally_executor.h b/base/task/promise/finally_executor.h
new file mode 100644
index 0000000..ec47b74
--- /dev/null
+++ b/base/task/promise/finally_executor.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 BASE_TASK_PROMISE_FINALLY_EXECUTOR_H_
+#define BASE_TASK_PROMISE_FINALLY_EXECUTOR_H_
+
+#include "base/task/promise/abstract_promise.h"
+#include "base/task/promise/helpers.h"
+
+namespace base {
+namespace internal {
+
+// Exists to reduce template bloat.
+class BASE_EXPORT FinallyExecutorCommon {
+ public:
+  explicit FinallyExecutorCommon(CallbackBase&& callback);
+  ~FinallyExecutorCommon();
+
+  // AbstractPromise::Executor:
+  bool IsCancelled() const;
+
+  CallbackBase callback_;
+};
+
+// A finally promise executor runs regardless of whether the prerequisite was
+// resolved or rejected. If the prerequsite is cancelled, the finally promise
+// and any dependents are cancelled too.
+template <typename CallbackT, typename ResolveStorage, typename RejectStorage>
+class FinallyExecutor {
+ public:
+  using CallbackReturnT = typename CallbackTraits<CallbackT>::ReturnType;
+
+  explicit FinallyExecutor(CallbackT&& callback)
+      : common_(std::move(callback)) {
+    static_assert(sizeof(CallbackBase) == sizeof(CallbackT),
+                  "We assume it's possible to cast from CallbackBase to "
+                  "CallbackT");
+  }
+
+  ~FinallyExecutor() = default;
+
+  bool IsCancelled() const { return common_.IsCancelled(); }
+
+  AbstractPromise::Executor::PrerequisitePolicy GetPrerequisitePolicy() const {
+    return AbstractPromise::Executor::PrerequisitePolicy::kAll;
+  }
+
+  void Execute(AbstractPromise* promise) {
+    AbstractPromise* prerequisite = promise->GetOnlyPrerequisite();
+    CallbackT* resolve_executor = static_cast<CallbackT*>(&common_.callback_);
+    RunHelper<CallbackT, void, ResolveStorage, RejectStorage>::Run(
+        std::move(*resolve_executor), prerequisite, promise);
+
+    if (promise->IsResolvedWithPromise() ||
+        promise->value().type() == TypeId::From<ResolveStorage>()) {
+      promise->OnResolved();
+    } else {
+      DCHECK_EQ(promise->value().type(), TypeId::From<RejectStorage>());
+      promise->OnRejected();
+    }
+  }
+
+#if DCHECK_IS_ON()
+  AbstractPromise::Executor::ArgumentPassingType ResolveArgumentPassingType()
+      const {
+    return AbstractPromise::Executor::ArgumentPassingType::kNormal;
+  }
+
+  AbstractPromise::Executor::ArgumentPassingType RejectArgumentPassingType()
+      const {
+    return AbstractPromise::Executor::ArgumentPassingType::kNormal;
+  }
+
+  bool CanResolve() const {
+    return PromiseCallbackTraits<CallbackReturnT>::could_resolve;
+  }
+
+  bool CanReject() const {
+    return PromiseCallbackTraits<CallbackReturnT>::could_reject;
+  }
+#endif
+
+ private:
+  FinallyExecutorCommon common_;
+};
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_TASK_PROMISE_FINALLY_EXECUTOR_H_
diff --git a/base/task/promise/helpers.h b/base/task/promise/helpers.h
index 8a1104d..62a351b 100644
--- a/base/task/promise/helpers.h
+++ b/base/task/promise/helpers.h
@@ -362,7 +362,7 @@
 // Helper for running a promise callback and storing the result if any.
 //
 // Callback = signature of the callback to execute,
-// ArgStorageType = type of the callback parameter (pr void if none)
+// ArgStorageType = type of the callback parameter (or void if none)
 // ResolveStorage = type to use for resolve, usually Resolved<T>.
 // RejectStorage = type to use for reject, usually Rejected<T>.
 // TODO(alexclarke): Add support for Rejected<Variant<...>>.
diff --git a/base/task/promise/promise.h b/base/task/promise/promise.h
index cb70a2f6..4ffb4298 100644
--- a/base/task/promise/promise.h
+++ b/base/task/promise/promise.h
@@ -6,6 +6,7 @@
 #define BASE_TASK_PROMISE_PROMISE_H_
 
 #include "base/task/post_task.h"
+#include "base/task/promise/finally_executor.h"
 #include "base/task/promise/helpers.h"
 #include "base/task/promise/no_op_promise_executor.h"
 #include "base/task/promise/promise_result.h"
@@ -300,6 +301,57 @@
                   std::forward<RejectCb>(on_reject));
   }
 
+  // A task to execute |finally_callback| on |task_runner| is posted after the
+  // parent promise is resolved or rejected. |finally_callback| is not executed
+  // if the parent promise is cancelled. Unlike the finally() in Javascript
+  // promises, this doesn't return a Promise that is resolved or rejected with
+  // the parent's value if |finally_callback| returns void. (We could support
+  // this if needed it but it seems unlikely to be used).
+  template <typename FinallyCb>
+  NOINLINE auto FinallyOn(scoped_refptr<TaskRunner> task_runner,
+                          const Location& from_here,
+                          FinallyCb&& finally_callback) noexcept {
+    DCHECK(abstract_promise_);
+
+    // Extract properties from |finally_callback| callback.
+    using CallbackTraits = internal::CallbackTraits<FinallyCb>;
+    using ReturnedPromiseResolveT = typename CallbackTraits::ResolveType;
+    using ReturnedPromiseRejectT = typename CallbackTraits::RejectType;
+
+    using CallbackArgT = typename CallbackTraits::ArgType;
+    static_assert(std::is_void<CallbackArgT>::value,
+                  "|finally_callback| callback must have no arguments");
+
+    return Promise<ReturnedPromiseResolveT, ReturnedPromiseRejectT>(
+        MakeRefCounted<internal::AbstractPromise>(
+            std::move(task_runner), from_here,
+            std::make_unique<internal::AbstractPromise::AdjacencyList>(
+                abstract_promise_),
+            RejectPolicy::kMustCatchRejection,
+            internal::AbstractPromise::ConstructWith<
+                internal::DependentList::ConstructUnresolved,
+                internal::FinallyExecutor<
+                    OnceCallback<typename CallbackTraits::ReturnType()>,
+                    Resolved<ReturnedPromiseResolveT>,
+                    Rejected<ReturnedPromiseRejectT>>>(),
+            std::forward<FinallyCb>(finally_callback)));
+  }
+
+  template <typename FinallyCb>
+  auto FinallyOn(const TaskTraits& traits,
+                 const Location& from_here,
+                 FinallyCb&& finally_callback) noexcept {
+    return FinallyOn(CreateTaskRunnerWithTraits(traits), from_here,
+                     std::move(finally_callback));
+  }
+
+  template <typename FinallyCb>
+  auto FinallyOnCurrent(const Location& from_here,
+                        FinallyCb&& finally_callback) noexcept {
+    return FinallyOn(SequencedTaskRunnerHandle::Get(), from_here,
+                     std::move(finally_callback));
+  }
+
   template <typename... Args>
   NOINLINE static Promise<ResolveType, RejectType> CreateResolved(
       const Location& from_here,
diff --git a/base/task/promise/promise_unittest.cc b/base/task/promise/promise_unittest.cc
index c041c1f..80b7ee0 100644
--- a/base/task/promise/promise_unittest.cc
+++ b/base/task/promise/promise_unittest.cc
@@ -1090,6 +1090,118 @@
   run_loop.Run();
 }
 
+TEST_F(PromiseTest, SettledTaskFinally) {
+  int result = 0;
+  ManualPromiseResolver<int> p(FROM_HERE);
+  p.Resolve(123);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE,
+                     BindLambdaForTesting([&](int value) { result = value; }))
+      .FinallyOnCurrent(FROM_HERE, BindLambdaForTesting([&]() {
+                          EXPECT_EQ(123, result);
+                          run_loop.Quit();
+                        }));
+
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, SettledTaskFinallyThen) {
+  int result = 0;
+  ManualPromiseResolver<int> p(FROM_HERE);
+  p.Resolve(123);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE,
+                     BindLambdaForTesting([&](int value) { result = value; }))
+      .FinallyOnCurrent(FROM_HERE, BindLambdaForTesting([&]() {
+                          EXPECT_EQ(123, result);
+                          return std::string("hi");
+                        }))
+      .ThenOnCurrent(FROM_HERE,
+                     BindLambdaForTesting([&](const std::string& value) {
+                       EXPECT_EQ("hi", value);
+                       run_loop.Quit();
+                     }));
+
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, SettledTaskFinallyCatch) {
+  int result = 0;
+  ManualPromiseResolver<int> p(FROM_HERE);
+  p.Resolve(123);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE,
+                     BindLambdaForTesting([&](int value) { result = value; }))
+      .FinallyOnCurrent(
+          FROM_HERE,
+          BindLambdaForTesting([&]() -> PromiseResult<void, std::string> {
+            EXPECT_EQ(123, result);
+            return std::string("Oh no");
+          }))
+      .CatchOnCurrent(FROM_HERE,
+                      BindLambdaForTesting([&](const std::string& value) {
+                        EXPECT_EQ("Oh no", value);
+                        run_loop.Quit();
+                      }));
+
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, ResolveFinally) {
+  int result = 0;
+  ManualPromiseResolver<int> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise().ThenOnCurrent(
+      FROM_HERE, BindLambdaForTesting([&](int value) { result = value; }));
+  p.promise().FinallyOnCurrent(FROM_HERE, BindLambdaForTesting([&]() {
+                                 EXPECT_EQ(123, result);
+                                 run_loop.Quit();
+                               }));
+  p.Resolve(123);
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, RejectFinally) {
+  int result = 0;
+  ManualPromiseResolver<int, void> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise().ThenOnCurrent(
+      FROM_HERE, BindLambdaForTesting([&](int value) { result = value; }),
+      BindLambdaForTesting([&]() { result = -1; }));
+  p.promise().FinallyOnCurrent(FROM_HERE, BindLambdaForTesting([&]() {
+                                 EXPECT_EQ(-1, result);
+                                 run_loop.Quit();
+                               }));
+  p.Reject();
+  run_loop.Run();
+}
+
+TEST_F(PromiseTest, RejectFinallySkipsThens) {
+  ManualPromiseResolver<void> p(FROM_HERE);
+
+  RunLoop run_loop;
+  p.promise()
+      .ThenOnCurrent(FROM_HERE,
+                     BindLambdaForTesting([&]() { return Rejected<int>(123); }))
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&]() {
+                       FAIL() << "Promise was rejected";
+                     }))
+      .ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&]() {
+                       FAIL() << "Promise was rejected";
+                     }))
+      .FinallyOnCurrent(FROM_HERE, run_loop.QuitClosure());
+  p.Resolve();
+  run_loop.Run();
+}
+
 namespace {
 struct Cancelable {
   Cancelable() : weak_ptr_factory(this) {}
@@ -1129,6 +1241,8 @@
                           log.push_back("Caught " + err);
                         }));
 
+    p2.FinallyOnCurrent(
+        FROM_HERE, BindLambdaForTesting([&]() { log.push_back("Finally"); }));
     p2.ThenOnCurrent(FROM_HERE,
                      BindLambdaForTesting([&]() { log.push_back("Then #5"); }));
     p2.ThenOnCurrent(FROM_HERE,
diff --git a/base/task/sequence_manager/sequence_manager_impl.cc b/base/task/sequence_manager/sequence_manager_impl.cc
index e30b78f..bce5c1d 100644
--- a/base/task/sequence_manager/sequence_manager_impl.cc
+++ b/base/task/sequence_manager/sequence_manager_impl.cc
@@ -437,6 +437,8 @@
       return "RunControlPriorityTask";
     case TaskQueue::QueuePriority::kHighestPriority:
       return "RunHighestPriorityTask";
+    case TaskQueue::QueuePriority::kVeryHighPriority:
+      return "RunVeryHighPriorityTask";
     case TaskQueue::QueuePriority::kHighPriority:
       return "RunHighPriorityTask";
     case TaskQueue::QueuePriority::kNormalPriority:
diff --git a/base/task/sequence_manager/sequence_manager_impl_unittest.cc b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
index f0f221b9..bd9f3f9 100644
--- a/base/task/sequence_manager/sequence_manager_impl_unittest.cc
+++ b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
@@ -4422,11 +4422,12 @@
 
   EXPECT_EQ(order,
             "000000000000000000000000000000000000000000000000000000000000"
-            "111121111213112141121113121111211412311121111211312411121111"
-            "231112114121131211112111123412222223222224223222222223242222"
-            "223222222423222222223433333343333334333333433333343333334333"
-            "333433333343333344444444444444444444444444444444444444444444"
-            "555555555555555555555555555555555555555555555555555555555555");
+            "111131211314121315113214131121311151324113112131114132511311"
+            "213411132115131241311121311145132111311214311253323432333233"
+            "453233323432333253432333233432353233432333233453233323422252"
+            "242222224252222422222245222224222254444445444444544444454444"
+            "445444444544444454445555555555555555555555555555555555555555"
+            "666666666666666666666666666666666666666666666666666666666666");
 }
 
 class CancelableTaskWithDestructionObserver {
diff --git a/base/task/sequence_manager/task_queue.h b/base/task/sequence_manager/task_queue.h
index 19838e0..673503b 100644
--- a/base/task/sequence_manager/task_queue.h
+++ b/base/task/sequence_manager/task_queue.h
@@ -83,17 +83,19 @@
     // and can starve the best effort queue.
     kHighestPriority = 1,
 
-    kHighPriority = 2,
+    kVeryHighPriority = 2,
+
+    kHighPriority = 3,
 
     // Queues with normal priority are the default.
-    kNormalPriority = 3,
-    kLowPriority = 4,
+    kNormalPriority = 4,
+    kLowPriority = 5,
 
     // Queues with best effort priority will only be run if all other queues are
     // empty. They can be starved by the other queues.
-    kBestEffortPriority = 5,
+    kBestEffortPriority = 6,
     // Must be the last entry.
-    kQueuePriorityCount = 6,
+    kQueuePriorityCount = 7,
     kFirstQueuePriority = kControlPriority,
   };
 
diff --git a/base/task/sequence_manager/task_queue_selector.h b/base/task/sequence_manager/task_queue_selector.h
index 334237f..e7cf55e 100644
--- a/base/task/sequence_manager/task_queue_selector.h
+++ b/base/task/sequence_manager/task_queue_selector.h
@@ -91,10 +91,14 @@
   // starved by delayed tasks.
   void SetImmediateStarvationCountForTest(size_t immediate_starvation_count);
 
-  // Maximum score to accumulate before high priority tasks are run even in
+  // Maximum score to accumulate before very high priority tasks are run even in
   // the presence of highest priority tasks.
   static const size_t kMaxHighPriorityStarvationScore = 3;
 
+  // Maximum score to accumulate before high priority tasks are run even in the
+  // presence of very high priority tasks.
+  static const size_t kMaxVeryHighPriorityStarvationScore = 5;
+
   // Maximum score to accumulate before normal priority tasks are run even in
   // the presence of higher priority tasks i.e. highest and high priority tasks.
   static const size_t kMaxNormalPriorityStarvationScore = 10;
@@ -267,6 +271,9 @@
           // kHighestPriority
           0,
 
+          // kVeryHighPriority
+          kMaxVeryHighPriorityStarvationScore,
+
           // kHighPriority
           kMaxHighPriorityStarvationScore,
 
diff --git a/base/test/bind_test_util.h b/base/test/bind_test_util.h
index f545159..85e1f91 100644
--- a/base/test/bind_test_util.h
+++ b/base/test/bind_test_util.h
@@ -5,6 +5,9 @@
 #ifndef BASE_TEST_BIND_TEST_UTIL_H_
 #define BASE_TEST_BIND_TEST_UTIL_H_
 
+#include <type_traits>
+#include <utility>
+
 #include "base/bind.h"
 #include "base/strings/string_piece.h"
 
@@ -14,6 +17,18 @@
 
 namespace internal {
 
+template <typename Callable,
+          typename Signature = decltype(&Callable::operator())>
+struct HasConstCallOperatorImpl : std::false_type {};
+
+template <typename Callable, typename R, typename... Args>
+struct HasConstCallOperatorImpl<Callable, R (Callable::*)(Args...) const>
+    : std::true_type {};
+
+template <typename Callable>
+constexpr bool HasConstCallOperator =
+    HasConstCallOperatorImpl<std::decay_t<Callable>>::value;
+
 template <typename F, typename Signature>
 struct BindLambdaHelper;
 
@@ -22,17 +37,40 @@
   static R Run(const std::decay_t<F>& f, Args... args) {
     return f(std::forward<Args>(args)...);
   }
+
+  static R RunOnce(std::decay_t<F>&& f, Args... args) {
+    return f(std::forward<Args>(args)...);
+  }
 };
 
 }  // namespace internal
 
 // A variant of BindRepeating() that can bind capturing lambdas for testing.
 // This doesn't support extra arguments binding as the lambda itself can do.
-template <typename F>
-decltype(auto) BindLambdaForTesting(F&& f) {
-  using Signature = internal::ExtractCallableRunType<std::decay_t<F>>;
-  return BindRepeating(&internal::BindLambdaHelper<F, Signature>::Run,
-                       std::forward<F>(f));
+template <typename Lambda,
+          std::enable_if_t<internal::HasConstCallOperator<Lambda>>* = nullptr>
+decltype(auto) BindLambdaForTesting(Lambda&& lambda) {
+  using Signature = internal::ExtractCallableRunType<std::decay_t<Lambda>>;
+  return BindRepeating(&internal::BindLambdaHelper<Lambda, Signature>::Run,
+                       std::forward<Lambda>(lambda));
+}
+
+// A variant of BindRepeating() that can bind mutable capturing lambdas for
+// testing. This doesn't support extra arguments binding as the lambda itself
+// can do. Since a mutable lambda potentially can invalidate its state after
+// being run once, this method returns a OnceCallback instead of a
+// RepeatingCallback.
+template <typename Lambda,
+          std::enable_if_t<!internal::HasConstCallOperator<Lambda>>* = nullptr>
+decltype(auto) BindLambdaForTesting(Lambda&& lambda) {
+  static_assert(
+      std::is_rvalue_reference<Lambda&&>() &&
+          !std::is_const<std::remove_reference_t<Lambda>>(),
+      "BindLambdaForTesting requires non-const rvalue for mutable lambda "
+      "binding. I.e.: base::BindLambdaForTesting(std::move(lambda)).");
+  using Signature = internal::ExtractCallableRunType<std::decay_t<Lambda>>;
+  return BindOnce(&internal::BindLambdaHelper<Lambda, Signature>::RunOnce,
+                  std::move(lambda));
 }
 
 // Returns a closure that fails on destruction if it hasn't been run.
diff --git a/base/test/launcher/test_launcher.cc b/base/test/launcher/test_launcher.cc
index 6f91488..9c1ee1f 100644
--- a/base/test/launcher/test_launcher.cc
+++ b/base/test/launcher/test_launcher.cc
@@ -153,7 +153,7 @@
   // from being spawned.
   AutoLock lock(*GetLiveProcessesLock());
 
-  fprintf(stdout, "Sending SIGTERM to %" PRIuS " child processes... ",
+  fprintf(stdout, "Sending SIGTERM to %zu child processes... ",
           GetLiveProcesses()->size());
   fflush(stdout);
 
@@ -171,7 +171,7 @@
   fprintf(stdout, "done.\n");
   fflush(stdout);
 
-  fprintf(stdout, "Sending SIGKILL to %" PRIuS " child processes... ",
+  fprintf(stdout, "Sending SIGKILL to %zu child processes... ",
           GetLiveProcesses()->size());
   fflush(stdout);
 
@@ -347,7 +347,7 @@
   // index).
   static base::AtomicSequenceNumber child_launch_index;
   base::FilePath nested_data_path = kDataPath.AppendASCII(
-      base::StringPrintf("test-%" PRIuS "-%d", base::Process::Current().Pid(),
+      base::StringPrintf("test-%zu-%d", base::Process::Current().Pid(),
                          child_launch_index.GetNext()));
   CHECK(!base::DirectoryExists(nested_data_path));
   CHECK(base::CreateDirectory(nested_data_path));
@@ -652,15 +652,13 @@
       total_shards_(1),
       shard_index_(0),
       cycles_(1),
-      test_found_count_(0),
+      broken_threshold_(0),
       test_started_count_(0),
       test_finished_count_(0),
       test_success_count_(0),
       test_broken_count_(0),
-      retry_count_(0),
       retry_limit_(0),
       force_run_broken_tests_(false),
-      run_result_(true),
       shuffle_(false),
       shuffle_seed_(0),
       watchdog_timer_(FROM_HERE,
@@ -681,9 +679,6 @@
                                       : command_line))
     return false;
 
-  // Value of |cycles_| changes after each iteration. Keep track of the
-  // original value.
-  int requested_cycles = cycles_;
 
 #if defined(OS_POSIX)
   CHECK_EQ(0, pipe(g_shutdown_pipe));
@@ -706,17 +701,38 @@
   // Start the watchdog timer.
   watchdog_timer_.Reset();
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, BindOnce(&TestLauncher::RunTestIteration, Unretained(this)));
+  // Indicate a test did not succeed.
+  bool test_failed = false;
+  int cycles = cycles_;
+  // Set to false if any iteration fails.
+  bool run_result = true;
 
-  RunLoop().Run();
+  while ((cycles > 0 || cycles == -1) && !(stop_on_failure_ && test_failed)) {
+    OnTestIterationStart();
 
-  if (requested_cycles != 1)
+    RunTests();
+    bool retry_result = RunRetryTests();
+    // Signal failure, but continue to run all requested test iterations.
+    // With the summary of all iterations at the end this is a good default.
+    run_result = run_result && retry_result;
+
+    if (retry_result) {
+      fprintf(stdout, "SUCCESS: all tests passed.\n");
+      fflush(stdout);
+    }
+
+    test_failed = test_success_count_ != test_finished_count_;
+    OnTestIterationFinished();
+    // Special value "-1" means "repeat indefinitely".
+    cycles = (cycles == -1) ? cycles : cycles - 1;
+  }
+
+  if (cycles_ != 1)
     results_tracker_.PrintSummaryOfAllIterations();
 
   MaybeSaveSummaryAsJSON(std::vector<std::string>());
 
-  return run_result_;
+  return run_result;
 }
 
 void TestLauncher::LaunchChildGTestProcess(
@@ -750,7 +766,7 @@
     // Keep the top and bottom of the log and truncate the middle part.
     result.output_snippet =
         result.output_snippet.substr(0, kOutputSnippetBytesLimit / 2) + "\n" +
-        StringPrintf("<truncated (%" PRIuS " bytes)>\n",
+        StringPrintf("<truncated (%zu bytes)>\n",
                      result.output_snippet.length()) +
         result.output_snippet.substr(result.output_snippet.length() -
                                      kOutputSnippetBytesLimit / 2) +
@@ -789,11 +805,9 @@
   results_tracker_.AddTestResult(result);
 
   // TODO(phajdan.jr): Align counter (padding).
-  std::string status_line(
-      StringPrintf("[%" PRIuS "/%" PRIuS "] %s ",
-                   test_finished_count_,
-                   test_started_count_,
-                   result.full_name.c_str()));
+  std::string status_line(StringPrintf("[%zu/%zu] %s ", test_finished_count_,
+                                       test_started_count_,
+                                       result.full_name.c_str()));
   if (result.completed()) {
     status_line.append(StringPrintf("(%" PRId64 " ms)",
                                     result.elapsed_time.InMilliseconds()));
@@ -822,10 +836,8 @@
       result.status == TestResult::TEST_UNKNOWN) {
     test_broken_count_++;
   }
-  size_t broken_threshold =
-      std::max(static_cast<size_t>(20), test_found_count_ / 10);
-  if (!force_run_broken_tests_ && test_broken_count_ >= broken_threshold) {
-    fprintf(stdout, "Too many badly broken tests (%" PRIuS "), exiting now.\n",
+  if (!force_run_broken_tests_ && test_broken_count_ >= broken_threshold_) {
+    fprintf(stdout, "Too many badly broken tests (%zu), exiting now.\n",
             test_broken_count_);
     fflush(stdout);
 
@@ -837,51 +849,8 @@
 
     exit(1);
   }
-
-  if (test_finished_count_ != test_started_count_)
-    return;
-
-  if (tests_to_retry_.empty() || retry_count_ >= retry_limit_) {
-    OnTestIterationFinished();
-    return;
-  }
-
-  if (!force_run_broken_tests_ && tests_to_retry_.size() >= broken_threshold) {
-    fprintf(stdout,
-            "Too many failing tests (%" PRIuS "), skipping retries.\n",
-            tests_to_retry_.size());
-    fflush(stdout);
-
-    results_tracker_.AddGlobalTag("BROKEN_TEST_SKIPPED_RETRIES");
-
-    OnTestIterationFinished();
-    return;
-  }
-
-  retry_count_++;
-
-  std::vector<std::string> test_names(tests_to_retry_.begin(),
-                                      tests_to_retry_.end());
-
-  tests_to_retry_.clear();
-
-  size_t retry_started_count = launcher_delegate_->RetryTests(this, test_names);
-  if (retry_started_count == 0) {
-    // Signal failure, but continue to run all requested test iterations.
-    // With the summary of all iterations at the end this is a good default.
-    run_result_ = false;
-
-    OnTestIterationFinished();
-    return;
-  }
-
-  fprintf(stdout, "Retrying %" PRIuS " test%s (retry #%" PRIuS ")\n",
-          retry_started_count,
-          retry_started_count > 1 ? "s" : "",
-          retry_count_);
-  fflush(stdout);
-
-  test_started_count_ += retry_started_count;
+  if (test_finished_count_ == test_started_count_)
+    RunLoop::QuitCurrentWhenIdleDeprecated();
 }
 
 // Helper used to parse test filter files. Syntax is documented in
@@ -1064,7 +1033,7 @@
     return false;
   }
 
-  fprintf(stdout, "Using %" PRIuS " parallel jobs.\n", parallel_jobs_);
+  fprintf(stdout, "Using %zu parallel jobs.\n", parallel_jobs_);
   fflush(stdout);
 
   CreateAndStartThreadPool(static_cast<int>(parallel_jobs_));
@@ -1311,6 +1280,7 @@
 
 void TestLauncher::RunTests() {
   std::vector<std::string> test_names;
+  size_t test_found_count = 0;
   for (const TestInfo& test_info : tests_) {
     std::string test_name = test_info.GetFullName();
     results_tracker_.AddTest(test_name);
@@ -1327,7 +1297,7 @@
       continue;
 
     // Count tests in the binary, before we apply filter and sharding.
-    test_found_count_++;
+    test_found_count++;
 
     std::string test_name_no_disabled = test_info.GetDisabledStrippedName();
 
@@ -1402,39 +1372,58 @@
   results_tracker_.GeneratePlaceholderIteration();
   MaybeSaveSummaryAsJSON({"EARLY_SUMMARY"});
 
+  broken_threshold_ = std::max(static_cast<size_t>(20), test_found_count / 10);
+
   test_started_count_ = launcher_delegate_->RunTests(this, test_names);
 
-  if (test_started_count_ == 0) {
-    fprintf(stdout, "0 tests run\n");
-    fflush(stdout);
-
-    // No tests have actually been started, so kick off the next iteration.
-    ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, BindOnce(&TestLauncher::RunTestIteration, Unretained(this)));
-  }
+  if (test_started_count_ > 0)
+    RunLoop().Run();
 }
 
-void TestLauncher::RunTestIteration() {
-  if (cycles_ == 0 ||
-      (stop_on_failure_ && test_success_count_ != test_finished_count_)) {
-    RunLoop::QuitCurrentWhenIdleDeprecated();
-    return;
+bool TestLauncher::RunRetryTests() {
+  // Number of retries in this iteration.
+  size_t retry_count = 0;
+  while (!tests_to_retry_.empty() && retry_count < retry_limit_) {
+    if (!force_run_broken_tests_ &&
+        tests_to_retry_.size() >= broken_threshold_) {
+      fprintf(stdout, "Too many failing tests (%zu), skipping retries.\n",
+              tests_to_retry_.size());
+      fflush(stdout);
+
+      results_tracker_.AddGlobalTag("BROKEN_TEST_SKIPPED_RETRIES");
+      return false;
+    }
+    std::vector<std::string> test_names(tests_to_retry_.begin(),
+                                        tests_to_retry_.end());
+    tests_to_retry_.clear();
+
+    size_t retry_started_count =
+        launcher_delegate_->RetryTests(this, test_names);
+
+    test_started_count_ += retry_started_count;
+
+    // Only invoke RunLoop if there are any tasks to run.
+    if (retry_started_count == 0)
+      return false;
+
+    fprintf(stdout, "Retrying %zu test%s (retry #%zu)\n", retry_started_count,
+            retry_started_count > 1 ? "s" : "", retry_count);
+    fflush(stdout);
+
+    RunLoop().Run();
+
+    retry_count++;
   }
+  return tests_to_retry_.empty();
+}
 
-  // Special value "-1" means "repeat indefinitely".
-  cycles_ = (cycles_ == -1) ? cycles_ : cycles_ - 1;
-
-  test_found_count_ = 0;
+void TestLauncher::OnTestIterationStart() {
   test_started_count_ = 0;
   test_finished_count_ = 0;
   test_success_count_ = 0;
   test_broken_count_ = 0;
-  retry_count_ = 0;
   tests_to_retry_.clear();
   results_tracker_.OnTestIterationStarting();
-
-  ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, BindOnce(&TestLauncher::RunTests, Unretained(this)));
 }
 
 #if defined(OS_POSIX)
@@ -1473,23 +1462,7 @@
   if (!tests_by_status[TestResult::TEST_UNKNOWN].empty())
     results_tracker_.AddGlobalTag(kUnreliableResultsTag);
 
-  // When we retry tests, success is determined by having nothing more
-  // to retry (everything eventually passed), as opposed to having
-  // no failures at all.
-  if (tests_to_retry_.empty()) {
-    fprintf(stdout, "SUCCESS: all tests passed.\n");
-    fflush(stdout);
-  } else {
-    // Signal failure, but continue to run all requested test iterations.
-    // With the summary of all iterations at the end this is a good default.
-    run_result_ = false;
-  }
-
   results_tracker_.PrintSummaryOfCurrentIteration();
-
-  // Kick off the next iteration.
-  ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, BindOnce(&TestLauncher::RunTestIteration, Unretained(this)));
 }
 
 void TestLauncher::OnOutputTimeout() {
diff --git a/base/test/launcher/test_launcher.h b/base/test/launcher/test_launcher.h
index ca60b9b..6e98926 100644
--- a/base/test/launcher/test_launcher.h
+++ b/base/test/launcher/test_launcher.h
@@ -192,10 +192,15 @@
<