diff --git a/DEPS b/DEPS
index 5a1b8b2..05634da 100644
--- a/DEPS
+++ b/DEPS
@@ -142,11 +142,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '4e6a4416e5df9dd16d2d39172511a6d45cc86e35',
+  'skia_revision': 'f6da14697e15a91e68c49571385bcc5d3bea8db3',
   # 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': 'e545aee43105b9d8278377f25c4b23a62a1282c5',
+  'v8_revision': '2206e55ad023327a744c06084a594944486d0ef8',
   # 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.
@@ -154,15 +154,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '8db211bc8d28156af958549c0d8a2669db87936a',
+  'angle_revision': '60e2f11eb584a80bcdd7e672e00cf7d1c594c5ee',
   # 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': '28ae0a4b2810b9f72547382333f32e0a46d87709',
+  'swiftshader_revision': '4cd9767e65651b636034aee688e502a8fbe6e559',
   # 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': '9231ac5e1c6884555571fa2541fcbc10ff97a180',
+  'pdfium_revision': '3ec47b651aaa7d2d45640280e4625824a643e669',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -205,7 +205,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': '3efcccc6e77a54776a37f796183aa92c575060b5',
+  'catapult_revision': '14d669b045c865befc9b4e8bcf1295b78522a7aa',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -806,7 +806,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '9ec162df61d3ff15a3913c5f616d5de538b82369',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '992dcfc16c3ca68a57d0fda3ca3838199898f468',
       'condition': 'checkout_linux',
   },
 
@@ -900,7 +900,7 @@
   },
 
   'src/third_party/glslang/src':
-    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + 'c538b5d796fb24dd418fdd650c7f76e56bcc3dd8',
+    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + '71892a5eda90fd7d3d6ccc12745f066d0ca5dc5f',
 
   'src/third_party/google_toolbox_for_mac/src': {
       'url': Var('chromium_git') + '/external/github.com/google/google-toolbox-for-mac.git' + '@' + Var('google_toolbox_for_mac_revision'),
@@ -1057,7 +1057,7 @@
   },
 
   'src/third_party/libjpeg_turbo':
-    Var('chromium_git') + '/chromium/deps/libjpeg_turbo.git' + '@' + '76aabbd351eea8a5988a5672526eda0677f2048d',
+    Var('chromium_git') + '/chromium/deps/libjpeg_turbo.git' + '@' + '14eba7addfdcf0699970fcbac225499858a167f2',
 
   'src/third_party/liblouis/src': {
       'url': Var('chromium_git') + '/external/liblouis-github.git' + '@' + '97ce1c67fccbd3668291b7e63c06161c095d49f2',
@@ -1191,7 +1191,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '67a281286c52bd9eb4158d0d66c33acb162e67d9',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'da391939c5248fde66b3c2c76345a7d671e4fb10',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1359,7 +1359,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '91350f8ecf9ab2922ee062c114e4a759f24bd8d0',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'ca160215c9d1f3fdb26cef072c01f12cd8b02fe6',
+    Var('webrtc_git') + '/src.git' + '@' + '0f0668e328995a62f2b7749150a67a6efa373d2e',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1400,7 +1400,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3bf592f177c2a2dc1b7629b1853a7a04f7d06516',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@360b8d795ccb20b62f22004ac105932c73231f51',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/gfx/surfaces_instance.cc b/android_webview/browser/gfx/surfaces_instance.cc
index 8e61bba..636d902 100644
--- a/android_webview/browser/gfx/surfaces_instance.cc
+++ b/android_webview/browser/gfx/surfaces_instance.cc
@@ -25,7 +25,6 @@
 #include "components/viz/service/display/display_scheduler.h"
 #include "components/viz/service/display_embedder/skia_output_surface_dependency.h"
 #include "components/viz/service/display_embedder/skia_output_surface_impl.h"
-#include "components/viz/service/display_embedder/skia_output_surface_impl_non_ddl.h"
 #include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
 #include "gpu/command_buffer/service/sequence_id.h"
@@ -258,11 +257,7 @@
                                                  nullptr /* gr_shader_cache */);
     }
     if (settings.use_skia_renderer_non_ddl) {
-      output_surface = std::make_unique<viz::SkiaOutputSurfaceImplNonDDL>(
-          gl_surface_, shared_context_state_, task_executor->mailbox_manager(),
-          task_executor->shared_image_manager(),
-          task_executor->sync_point_manager(),
-          false /* need_swapbuffers_ack */);
+      NOTIMPLEMENTED();
     } else {
       output_surface = std::make_unique<viz::SkiaOutputSurfaceImpl>(
           std::make_unique<SkiaOutputSurfaceDependencyWebView>(
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java b/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java
index ef3dc7c..bd3904a 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java
@@ -392,11 +392,35 @@
 
     @Override
     public boolean onConsoleMessage(AwConsoleMessage consoleMessage) {
-        if (TRACE) Log.i(TAG, "onConsoleMessage " + consoleMessage);
+        // Log unconditionally, because JavaScript errors also generate ConsoleMessages (and
+        // developers generally expect logcat to show such errors).
+        logConsoleMessage(consoleMessage);
         mAddMessageToConsoleHelper.notifyCalled(consoleMessage);
         return false;
     }
 
+    private void logConsoleMessage(AwConsoleMessage consoleMessage) {
+        String formattedMessage = "[" + consoleMessage.sourceId() + ":"
+                + consoleMessage.lineNumber() + "] " + consoleMessage.message();
+        switch (consoleMessage.messageLevel()) {
+            case AwConsoleMessage.MESSAGE_LEVEL_TIP:
+            case AwConsoleMessage.MESSAGE_LEVEL_LOG:
+                Log.i(TAG, "onConsoleMessage " + formattedMessage);
+                break;
+            case AwConsoleMessage.MESSAGE_LEVEL_WARNING:
+                Log.w(TAG, "onConsoleMessage " + formattedMessage);
+                break;
+            case AwConsoleMessage.MESSAGE_LEVEL_ERROR:
+                Log.e(TAG, "onConsoleMessage " + formattedMessage);
+                break;
+            default:
+                // Should not be reached, but fall-through anyway.
+            case AwConsoleMessage.MESSAGE_LEVEL_DEBUG:
+                Log.d(TAG, "onConsoleMessage " + formattedMessage);
+                break;
+        }
+    }
+
     /**
      * Callback helper for AddMessageToConsole.
      */
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 11baf96..9f5a8699 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -494,6 +494,8 @@
     "multi_user/user_switch_animator.h",
     "policy/policy_recommendation_restorer.cc",
     "policy/policy_recommendation_restorer.h",
+    "power/gatt_battery_percentage_fetcher.cc",
+    "power/gatt_battery_percentage_fetcher.h",
     "root_window_controller.cc",
     "root_window_settings.cc",
     "root_window_settings.h",
@@ -531,8 +533,6 @@
     "shelf/home_button.h",
     "shelf/home_button_controller.cc",
     "shelf/home_button_controller.h",
-    "shelf/kiosk_next_shelf_view.cc",
-    "shelf/kiosk_next_shelf_view.h",
     "shelf/login_shelf_view.cc",
     "shelf/login_shelf_view.h",
     "shelf/overflow_bubble.cc",
@@ -785,8 +785,6 @@
     "system/night_light/night_light_controller_impl.h",
     "system/night_light/night_light_feature_pod_controller.cc",
     "system/night_light/night_light_feature_pod_controller.h",
-    "system/night_light/night_light_toggle_button.cc",
-    "system/night_light/night_light_toggle_button.h",
     "system/night_light/time_of_day.cc",
     "system/night_light/time_of_day.h",
     "system/overview/overview_button_tray.cc",
@@ -847,8 +845,6 @@
     "system/power/power_prefs.h",
     "system/power/power_status.cc",
     "system/power/power_status.h",
-    "system/power/power_status_view.cc",
-    "system/power/power_status_view.h",
     "system/power/scoped_backlights_forced_off.cc",
     "system/power/scoped_backlights_forced_off.h",
     "system/power/tray_power.cc",
@@ -1697,6 +1693,7 @@
     "metrics/user_metrics_recorder_unittest.cc",
     "multi_device_setup/multi_device_notification_presenter_unittest.cc",
     "policy/policy_recommendation_restorer_unittest.cc",
+    "power/gatt_battery_percentage_fetcher_unittest.cc",
     "root_window_controller_unittest.cc",
     "rotator/screen_rotation_animation_unittest.cc",
     "rotator/screen_rotation_animator_unittest.cc",
@@ -1768,7 +1765,6 @@
     "system/power/power_notification_controller_unittest.cc",
     "system/power/power_prefs_unittest.cc",
     "system/power/power_status_unittest.cc",
-    "system/power/power_status_view_unittest.cc",
     "system/power/video_activity_notifier_unittest.cc",
     "system/rotation/rotation_lock_feature_pod_controller_unittest.cc",
     "system/screen_layout_observer_unittest.cc",
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index 5d721be..89ec2df 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -26,7 +26,6 @@
 #include "ash/ime/ime_controller.h"
 #include "ash/ime/ime_switch_type.h"
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
-#include "ash/kiosk_next/kiosk_next_shell_controller_impl.h"
 #include "ash/magnifier/docked_magnifier_controller_impl.h"
 #include "ash/magnifier/magnification_controller.h"
 #include "ash/media/media_controller_impl.h"
@@ -1441,10 +1440,6 @@
     actions_allowed_in_pinned_mode_.insert(
         kActionsAllowedInAppModeOrPinnedMode[i]);
   }
-  for (size_t i = 0; i < kActionsAllowedForKioskNextShellLength; i++) {
-    actions_allowed_for_kiosk_next_shell_.insert(
-        kActionsAllowedForKioskNextShell[i]);
-  }
   for (size_t i = 0; i < kActionsAllowedInPinnedModeLength; ++i)
     actions_allowed_in_pinned_mode_.insert(kActionsAllowedInPinnedMode[i]);
   for (size_t i = 0; i < kActionsNeedingWindowLength; ++i)
@@ -2016,10 +2011,6 @@
 AcceleratorControllerImpl::AcceleratorProcessingRestriction
 AcceleratorControllerImpl::GetAcceleratorProcessingRestriction(
     int action) const {
-  if (Shell::Get()->kiosk_next_shell_controller()->IsEnabled() &&
-      actions_allowed_for_kiosk_next_shell_.count(action) == 0) {
-    return RESTRICTION_PREVENT_PROCESSING_AND_PROPAGATION;
-  }
   if (Shell::Get()->screen_pinning_controller()->IsPinned() &&
       actions_allowed_in_pinned_mode_.find(action) ==
           actions_allowed_in_pinned_mode_.end()) {
diff --git a/ash/accelerators/accelerator_controller_impl.h b/ash/accelerators/accelerator_controller_impl.h
index 4807125..d2c36991 100644
--- a/ash/accelerators/accelerator_controller_impl.h
+++ b/ash/accelerators/accelerator_controller_impl.h
@@ -304,8 +304,6 @@
   std::set<int> actions_allowed_in_app_mode_;
   // Actions allowed in pinned mode.
   std::set<int> actions_allowed_in_pinned_mode_;
-  // Actions allowed when Kiosk Next Shell is enabled.
-  std::set<int> actions_allowed_for_kiosk_next_shell_;
   // Actions disallowed if there are no windows.
   std::set<int> actions_needing_window_;
   // Actions that can be performed without closing the menu (if one is present).
diff --git a/ash/accelerators/accelerator_table.cc b/ash/accelerators/accelerator_table.cc
index d47adc8..011e826 100644
--- a/ash/accelerators/accelerator_table.cc
+++ b/ash/accelerators/accelerator_table.cc
@@ -375,34 +375,4 @@
 const size_t kActionsKeepingMenuOpenLength =
     base::size(kActionsKeepingMenuOpen);
 
-const AcceleratorAction kActionsAllowedForKioskNextShell[] = {
-    BRIGHTNESS_DOWN,
-    BRIGHTNESS_UP,
-    DEBUG_TOGGLE_SHOW_DEBUG_BORDERS,
-    DEBUG_TOGGLE_SHOW_FPS_COUNTER,
-    DEBUG_TOGGLE_SHOW_PAINT_RECTS,
-    KEYBOARD_BRIGHTNESS_DOWN,
-    KEYBOARD_BRIGHTNESS_UP,
-    MEDIA_NEXT_TRACK,
-    MEDIA_PLAY_PAUSE,
-    MEDIA_PREV_TRACK,
-    POWER_PRESSED,
-    POWER_RELEASED,
-    TAKE_PARTIAL_SCREENSHOT,
-    TAKE_SCREENSHOT,
-    TAKE_WINDOW_SCREENSHOT,
-    TOGGLE_CAPS_LOCK,
-    TOGGLE_DICTATION,
-    TOGGLE_DOCKED_MAGNIFIER,
-    TOGGLE_FULLSCREEN_MAGNIFIER,
-    TOGGLE_HIGH_CONTRAST,
-    TOGGLE_SPOKEN_FEEDBACK,
-    VOLUME_DOWN,
-    VOLUME_MUTE,
-    VOLUME_UP,
-};
-
-const size_t kActionsAllowedForKioskNextShellLength =
-    base::size(kActionsAllowedForKioskNextShell);
-
 }  // namespace ash
diff --git a/ash/accelerators/accelerator_table.h b/ash/accelerators/accelerator_table.h
index 6586c52..82278e1 100644
--- a/ash/accelerators/accelerator_table.h
+++ b/ash/accelerators/accelerator_table.h
@@ -155,10 +155,6 @@
 ASH_EXPORT extern const AcceleratorAction kActionsKeepingMenuOpen[];
 ASH_EXPORT extern const size_t kActionsKeepingMenuOpenLength;
 
-// Actions that can be performed when the Kiosk Next Shell is enabled.
-ASH_EXPORT extern const AcceleratorAction kActionsAllowedForKioskNextShell[];
-ASH_EXPORT extern const size_t kActionsAllowedForKioskNextShellLength;
-
 }  // namespace ash
 
 #endif  // ASH_ACCELERATORS_ACCELERATOR_TABLE_H_
diff --git a/ash/accelerators/pre_target_accelerator_handler.cc b/ash/accelerators/pre_target_accelerator_handler.cc
index 4b35dd1..55f2ca8 100644
--- a/ash/accelerators/pre_target_accelerator_handler.cc
+++ b/ash/accelerators/pre_target_accelerator_handler.cc
@@ -8,7 +8,6 @@
 #include "ash/shell.h"
 #include "ash/wm/window_state.h"
 #include "base/feature_list.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/stl_util.h"
 #include "media/base/media_switches.h"
 #include "ui/aura/window.h"
@@ -56,7 +55,6 @@
   aura::Window* target = static_cast<aura::Window*>(key_event.target());
   // Callers should never supply null.
   DCHECK(target);
-  RecordSearchKeyStats(accelerator);
   // Special hardware keys like brightness and volume are handled in
   // special way. However, some windows can override this behavior
   // (e.g. Chrome v1 apps by default and Chrome v2 apps with
@@ -73,26 +71,6 @@
   return Shell::Get()->accelerator_controller()->Process(accelerator);
 }
 
-void PreTargetAcceleratorHandler::RecordSearchKeyStats(
-    const ui::Accelerator& accelerator) {
-  if (accelerator.IsCmdDown()) {
-    if (search_key_state_ == RELEASED) {
-      search_key_state_ = PRESSED;
-      search_key_pressed_timestamp_ = base::TimeTicks::Now();
-    }
-
-    if (accelerator.key_code() != ui::KeyboardCode::VKEY_COMMAND &&
-        search_key_state_ == PRESSED) {
-      search_key_state_ = RECORDED;
-      UMA_HISTOGRAM_TIMES(
-          "Keyboard.Shortcuts.CrosSearchKeyDelay",
-          base::TimeTicks::Now() - search_key_pressed_timestamp_);
-    }
-  } else {
-    search_key_state_ = RELEASED;
-  }
-}
-
 bool PreTargetAcceleratorHandler::CanConsumeSystemKeys(
     aura::Window* target,
     const ui::KeyEvent& event) {
diff --git a/ash/accelerators/pre_target_accelerator_handler.h b/ash/accelerators/pre_target_accelerator_handler.h
index 8640067..daebb82 100644
--- a/ash/accelerators/pre_target_accelerator_handler.h
+++ b/ash/accelerators/pre_target_accelerator_handler.h
@@ -7,7 +7,6 @@
 
 #include "ash/ash_export.h"
 #include "base/macros.h"
-#include "base/time/time.h"
 #include "ui/wm/core/accelerator_delegate.h"
 
 namespace aura {
@@ -46,14 +45,6 @@
                                    const ui::KeyEvent& event,
                                    const ui::Accelerator& accelerator);
 
-  // Records a histogram on how long the "Search" key is held when a user
-  // presses an accelerator that involes the "Search" key.
-  void RecordSearchKeyStats(const ui::Accelerator& accelerator);
-
-  enum SearchKeyState { RELEASED = 0, PRESSED, RECORDED };
-  SearchKeyState search_key_state_ = RELEASED;
-  base::TimeTicks search_key_pressed_timestamp_;
-
   DISALLOW_COPY_AND_ASSIGN(PreTargetAcceleratorHandler);
 };
 
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index b8e4b19..aa54d78 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -228,9 +228,6 @@
       <message name="IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_SETTINGS_TOOLTIP" desc="The tooltip text used for the button in the status tray to show the Night Light feature (which controls the color temperature of the screen) settings.">
         Show Night Light settings
       </message>
-      <message name="IDS_ASH_STATUS_TRAY_NIGHT_LIGHT" desc="The label used for the button in the status tray to toggle the Night Light feature (which controls the color temperature of the screen) on or off.">
-        Night Light: <ph name="NIGHT_LIGHT_STATUS">$1<ex>On</ex></ph>
-      </message>
       <message name="IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_OFF_STATE" desc="The label for the Off state of the Night Light feature.">
         Off
       </message>
diff --git a/ash/power/DEPS b/ash/power/DEPS
new file mode 100644
index 0000000..b299852d
--- /dev/null
+++ b/ash/power/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/device_event_log/device_event_log.h",
+]
diff --git a/ash/power/gatt_battery_percentage_fetcher.cc b/ash/power/gatt_battery_percentage_fetcher.cc
new file mode 100644
index 0000000..a651ab8
--- /dev/null
+++ b/ash/power/gatt_battery_percentage_fetcher.cc
@@ -0,0 +1,266 @@
+// 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/power/gatt_battery_percentage_fetcher.h"
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "components/device_event_log/device_event_log.h"
+#include "device/bluetooth/bluetooth_gatt_connection.h"
+#include "device/bluetooth/bluetooth_remote_gatt_characteristic.h"
+#include "device/bluetooth/bluetooth_remote_gatt_service.h"
+#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
+
+using device::BluetoothDevice;
+using device::BluetoothGattService;
+
+namespace ash {
+
+namespace {
+
+// UUIDs for the standardized Battery Service and Characteristic, defined by the
+// Bluetooth GATT Specification.
+// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Services/org.bluetooth.service.battery_service.xml
+constexpr char kBatteryServiceUUID[] = "180F";
+constexpr char kBatteryLevelUUID[] = "2A19";
+
+GattBatteryPercentageFetcher::Factory* g_test_factory_instance_ = nullptr;
+
+const device::BluetoothUUID& GetBatteryServiceUUID() {
+  static const device::BluetoothUUID battery_service_uuid(kBatteryServiceUUID);
+  return battery_service_uuid;
+}
+const device::BluetoothUUID& GetBatteryLevelUUID() {
+  static const device::BluetoothUUID battery_level_uuid(kBatteryLevelUUID);
+  return battery_level_uuid;
+}
+
+const char* BluetoothDeviceErrorCodeToString(
+    BluetoothDevice::ConnectErrorCode error_code) {
+  switch (error_code) {
+    case BluetoothDevice::ERROR_AUTH_CANCELED:
+      return "ERROR_AUTH_CANCELED";
+    case BluetoothDevice::ERROR_AUTH_FAILED:
+      return "ERROR_AUTH_FAILED";
+    case BluetoothDevice::ERROR_AUTH_REJECTED:
+      return "ERROR_AUTH_REJECTED";
+    case BluetoothDevice::ERROR_AUTH_TIMEOUT:
+      return "ERROR_AUTH_TIMEOUT";
+    case BluetoothDevice::ERROR_FAILED:
+      return "ERROR_FAILED";
+    case BluetoothDevice::ERROR_INPROGRESS:
+      return "ERROR_INPROGRESS";
+    case BluetoothDevice::ERROR_UNKNOWN:
+      return "ERROR_UNKNOWN";
+    case BluetoothDevice::ERROR_UNSUPPORTED_DEVICE:
+      return "ERROR_UNSUPPORTED_DEVICE";
+    case BluetoothDevice::NUM_CONNECT_ERROR_CODES:
+      NOTREACHED();
+      return "";
+  }
+}
+
+const char* GattErrorCodeToString(
+    BluetoothGattService::GattErrorCode error_code) {
+  switch (error_code) {
+    case BluetoothGattService::GATT_ERROR_UNKNOWN:
+      return "GATT_ERROR_UNKNOWN";
+    case BluetoothGattService::GATT_ERROR_FAILED:
+      return "GATT_ERROR_FAILED";
+    case BluetoothGattService::GATT_ERROR_IN_PROGRESS:
+      return "GATT_ERROR_IN_PROGRESS";
+    case BluetoothGattService::GATT_ERROR_INVALID_LENGTH:
+      return "GATT_ERROR_INVALID_LENGTH";
+    case BluetoothGattService::GATT_ERROR_NOT_PERMITTED:
+      return "GATT_ERROR_NOT_PERMITTED";
+    case BluetoothGattService::GATT_ERROR_NOT_AUTHORIZED:
+      return "GATT_ERROR_NOT_AUTHORIZED";
+    case BluetoothGattService::GATT_ERROR_NOT_PAIRED:
+      return "GATT_ERROR_NOT_PAIRED";
+    case BluetoothGattService::GATT_ERROR_NOT_SUPPORTED:
+      return "GATT_ERROR_NOT_SUPPORTED";
+  }
+}
+
+device::BluetoothRemoteGattService* GetGattBatteryService(
+    BluetoothDevice* device) {
+  for (device::BluetoothRemoteGattService* service :
+       device->GetGattServices()) {
+    if (service->GetUUID() == GetBatteryServiceUUID())
+      return service;
+  }
+  return nullptr;
+}
+
+}  // namespace
+
+// static
+std::unique_ptr<GattBatteryPercentageFetcher>
+GattBatteryPercentageFetcher::Factory::NewInstance(
+    scoped_refptr<device::BluetoothAdapter> adapter,
+    const std::string& device_address,
+    BatteryPercentageCallback callback) {
+  if (g_test_factory_instance_) {
+    return g_test_factory_instance_->BuildInstance(adapter, device_address,
+                                                   std::move(callback));
+  }
+  auto instance = base::WrapUnique(
+      new GattBatteryPercentageFetcher(device_address, std::move(callback)));
+  instance->SetAdapterAndStartFetching(adapter);
+  return instance;
+}
+
+// static
+void GattBatteryPercentageFetcher::Factory::SetFactoryForTesting(
+    Factory* factory) {
+  g_test_factory_instance_ = factory;
+}
+
+GattBatteryPercentageFetcher::GattBatteryPercentageFetcher(
+    const std::string& device_address,
+    BatteryPercentageCallback callback)
+    : device_address_(device_address), callback_(std::move(callback)) {}
+
+GattBatteryPercentageFetcher::~GattBatteryPercentageFetcher() {
+  if (adapter_)
+    adapter_->RemoveObserver(this);
+}
+
+void GattBatteryPercentageFetcher::SetAdapterAndStartFetching(
+    scoped_refptr<device::BluetoothAdapter> adapter) {
+  DCHECK(adapter);
+  adapter_ = adapter;
+  adapter_->AddObserver(this);
+
+  // Create Gatt Connection.
+  BluetoothDevice* device = adapter_->GetDevice(device_address());
+  if (!device) {
+    BLUETOOTH_LOG(ERROR)
+        << "GattBatteryPercentageFetcher error for device: " << device_address()
+        << ". Unable to get device from adapter on CreateGattConnection.";
+    InvokeCallbackWithFailedFetch();
+    return;
+  }
+  DCHECK(!connection_);
+  device->CreateGattConnection(
+      base::Bind(&GattBatteryPercentageFetcher::OnGattConnected,
+                 weak_ptr_factory_.GetWeakPtr()),
+      base::Bind(&GattBatteryPercentageFetcher::OnGattConnectError,
+                 weak_ptr_factory_.GetWeakPtr()));
+}
+
+void GattBatteryPercentageFetcher::OnGattConnected(
+    std::unique_ptr<device::BluetoothGattConnection> connection) {
+  DCHECK_EQ(connection->GetDeviceAddress(), this->device_address());
+  BluetoothDevice* device = adapter_->GetDevice(device_address());
+  if (!device) {
+    BLUETOOTH_LOG(ERROR)
+        << "GattBatteryPercentageFetcher error for device: " << device_address()
+        << ". Unable to get device from adapter on OnGattConnected.";
+    InvokeCallbackWithFailedFetch();
+    return;
+  }
+  connection_ = std::move(connection);
+
+  // If Gatt Services Discovery is not complete yet, wait until
+  // GattServicesDiscovered() is called to continue fetching the battery level.
+  if (device->IsGattServicesDiscoveryComplete())
+    AttemptToReadBatteryCharacteristic();
+}
+
+void GattBatteryPercentageFetcher::OnGattConnectError(
+    BluetoothDevice::ConnectErrorCode error_code) {
+  BLUETOOTH_LOG(ERROR) << "GattBatteryPercentageFetcher error for device: "
+                       << device_address() << ". OnGattConnectError"
+                       << BluetoothDeviceErrorCodeToString(error_code);
+  InvokeCallbackWithFailedFetch();
+}
+
+void GattBatteryPercentageFetcher::GattServicesDiscovered(
+    device::BluetoothAdapter* adapter,
+    BluetoothDevice* device) {
+  if (device->GetAddress() == device_address())
+    AttemptToReadBatteryCharacteristic();
+}
+
+void GattBatteryPercentageFetcher::AttemptToReadBatteryCharacteristic() {
+  // This function should only be called once with an active GATT connection.
+  if (!connection_ || attempted_to_read_the_battery_characteristic_)
+    return;
+
+  attempted_to_read_the_battery_characteristic_ = true;
+  BluetoothDevice* device = adapter_->GetDevice(device_address());
+  if (!device) {
+    BLUETOOTH_LOG(ERROR) << "GattBatteryPercentageFetcher error for device: "
+                         << device_address()
+                         << ". Unable to get device from adapter on "
+                            "AttemptToReadBatteryCharacteristic.";
+    InvokeCallbackWithFailedFetch();
+    return;
+  }
+
+  device::BluetoothRemoteGattService* service = GetGattBatteryService(device);
+  if (!service) {
+    BLUETOOTH_LOG(ERROR) << "GattBatteryPercentageFetcher error for device: "
+                         << device_address() << ". No battery service.";
+    InvokeCallbackWithFailedFetch();
+    return;
+  }
+
+  std::vector<device::BluetoothRemoteGattCharacteristic*> characteristics =
+      service->GetCharacteristicsByUUID(GetBatteryLevelUUID());
+
+  // If no battery characteristic exists, the value cannot be retrieved.
+  if (characteristics.empty()) {
+    BLUETOOTH_LOG(ERROR) << "GattBatteryPercentageFetcher error for device: "
+                         << device_address()
+                         << ". Bad format for battery level characteristic.";
+    InvokeCallbackWithFailedFetch();
+    return;
+  }
+
+  // Only one characteristic is expected to exist according to the GATT Battery
+  // Service standard. If multiple characteristics are present, arbitrarily
+  // choose the first one.
+  characteristics[0]->ReadRemoteCharacteristic(
+      base::Bind(&GattBatteryPercentageFetcher::OnReadBatteryLevel,
+                 weak_ptr_factory_.GetWeakPtr()),
+      base::Bind(&GattBatteryPercentageFetcher::OnReadBatteryLevelError,
+                 weak_ptr_factory_.GetWeakPtr()));
+}
+
+void GattBatteryPercentageFetcher::OnReadBatteryLevel(
+    const std::vector<uint8_t>& value) {
+  if (value.size() != 1 || value[0] > 100) {
+    BLUETOOTH_LOG(ERROR) << "GattBatteryPercentageFetcher error for device: "
+                         << device_address()
+                         << ". Wrong format for battery level.";
+    InvokeCallbackWithFailedFetch();
+    return;
+  }
+  InvokeCallbackWithSuccessfulFetch(value[0]);
+}
+
+void GattBatteryPercentageFetcher::OnReadBatteryLevelError(
+    BluetoothGattService::GattErrorCode error_code) {
+  BLUETOOTH_LOG(ERROR) << "GattBatteryPercentageFetcher error for device: "
+                       << device_address() << ". OnReadBatteryLevelError - "
+                       << GattErrorCodeToString(error_code);
+  InvokeCallbackWithFailedFetch();
+}
+
+void GattBatteryPercentageFetcher::InvokeCallbackWithSuccessfulFetch(
+    uint8_t battery_percentage) {
+  connection_.reset();
+  DCHECK(callback_);
+  std::move(callback_).Run(battery_percentage);
+}
+
+void GattBatteryPercentageFetcher::InvokeCallbackWithFailedFetch() {
+  connection_.reset();
+  DCHECK(callback_);
+  std::move(callback_).Run(base::nullopt);
+}
+
+}  // namespace ash
diff --git a/ash/power/gatt_battery_percentage_fetcher.h b/ash/power/gatt_battery_percentage_fetcher.h
new file mode 100644
index 0000000..1848369
--- /dev/null
+++ b/ash/power/gatt_battery_percentage_fetcher.h
@@ -0,0 +1,110 @@
+// 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_POWER_GATT_BATTERY_PERCENTAGE_FETCHER_H_
+#define ASH_POWER_GATT_BATTERY_PERCENTAGE_FETCHER_H_
+
+#include <string>
+
+#include "ash/ash_export.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/bluetooth_gatt_service.h"
+
+namespace device {
+class BluetoothGattConnection;
+}  // namespace device
+
+namespace ash {
+
+// Using the GATT Battery Service (BAS), returns the battery percentage (or
+// nullopt in case of error) via the |callback_| for a Bluetooth device with the
+// provided |device_address_|. An instance of this class should be created every
+// time such a value is needed and destroyed after the callback is called.
+class ASH_EXPORT GattBatteryPercentageFetcher
+    : public device::BluetoothAdapter::Observer {
+ public:
+  // Passes null if the battery percentage cannot be fetched.
+  using BatteryPercentageCallback =
+      base::OnceCallback<void(base::Optional<uint8_t>)>;
+
+  class Factory {
+   public:
+    virtual ~Factory() = default;
+
+    static void SetFactoryForTesting(Factory* factory);
+
+    static std::unique_ptr<GattBatteryPercentageFetcher> NewInstance(
+        scoped_refptr<device::BluetoothAdapter> adapter,
+        const std::string& device_address,
+        BatteryPercentageCallback callback);
+
+    virtual std::unique_ptr<GattBatteryPercentageFetcher> BuildInstance(
+        scoped_refptr<device::BluetoothAdapter> adapter,
+        const std::string& device_address,
+        BatteryPercentageCallback callback) = 0;
+  };
+
+  ~GattBatteryPercentageFetcher() override;
+
+  const std::string& device_address() const { return device_address_; }
+
+ protected:
+  GattBatteryPercentageFetcher(const std::string& device_address,
+                               BatteryPercentageCallback callback);
+
+  // Close |connection_| and return the fetched value through the callback.
+  void InvokeCallbackWithSuccessfulFetch(uint8_t battery_percentage);
+  void InvokeCallbackWithFailedFetch();
+
+ private:
+  friend class GattBatteryPercentageFetcherTest;
+
+  // device::BluetoothAdapter::Observer:
+  void GattServicesDiscovered(device::BluetoothAdapter* adapter,
+                              device::BluetoothDevice* device) override;
+
+  // Calling this function starts the fetching process. This allows tests to
+  // to create instances of this class without running the whole mechanism.
+  void SetAdapterAndStartFetching(
+      scoped_refptr<device::BluetoothAdapter> adapter);
+
+  // Checks if the GATT Services are discovered to gather the battery value,
+  // otherwise sets a flag to wait for them to complete.
+  void OnGattConnected(
+      std::unique_ptr<device::BluetoothGattConnection> connection);
+  void OnGattConnectError(device::BluetoothDevice::ConnectErrorCode error_code);
+
+  // Searches for the GATT Battery Service and Characteristic and requests to
+  // read its value.
+  void AttemptToReadBatteryCharacteristic();
+
+  // Callback when reading the battery percentage succeeds. Will return such
+  // value via |callback_|.
+  void OnReadBatteryLevel(const std::vector<uint8_t>& value);
+  void OnReadBatteryLevelError(
+      device::BluetoothGattService::GattErrorCode error_code);
+
+  const std::string device_address_;
+  BatteryPercentageCallback callback_;
+
+  // May be null in tests.
+  scoped_refptr<device::BluetoothAdapter> adapter_;
+  std::unique_ptr<device::BluetoothGattConnection> connection_;
+
+  // Flag to avoid fetching the battery level multiple times in case
+  // GattServicesDiscovered() is called more than once.
+  bool attempted_to_read_the_battery_characteristic_ = false;
+
+  base::WeakPtrFactory<GattBatteryPercentageFetcher> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(GattBatteryPercentageFetcher);
+};
+
+}  // namespace ash
+
+#endif  // ASH_POWER_GATT_BATTERY_PERCENTAGE_FETCHER_H_
diff --git a/ash/power/gatt_battery_percentage_fetcher_unittest.cc b/ash/power/gatt_battery_percentage_fetcher_unittest.cc
new file mode 100644
index 0000000..ff64040
--- /dev/null
+++ b/ash/power/gatt_battery_percentage_fetcher_unittest.cc
@@ -0,0 +1,258 @@
+// 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/power/gatt_battery_percentage_fetcher.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_connection.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using device::BluetoothDevice;
+using device::BluetoothRemoteGattCharacteristic;
+using testing::_;
+using testing::DoAll;
+using testing::NiceMock;
+using testing::Return;
+using testing::SaveArg;
+
+namespace {
+
+constexpr char kServiceID[] = "service id";
+constexpr char kCharacteristicID[] = "characteristic id";
+constexpr char kDeviceAddress[] = "AA:BB:CC:DD:EE:FF";
+constexpr char kBatteryServiceUUID[] = "180F";
+constexpr char kBatteryLevelUUID[] = "2A19";
+const uint8_t kBatteryPercentage = 100;
+
+ACTION_TEMPLATE(MoveArg,
+                HAS_1_TEMPLATE_PARAMS(int, k),
+                AND_1_VALUE_PARAMS(pointer)) {
+  *pointer = std::move(::std::get<k>(args));
+}
+
+const device::BluetoothUUID& GetBatteryServiceUUID() {
+  static const device::BluetoothUUID battery_service_uuid(kBatteryServiceUUID);
+  return battery_service_uuid;
+}
+
+const device::BluetoothUUID& GetBatteryLevelUUID() {
+  static const device::BluetoothUUID battery_level_uuid(kBatteryLevelUUID);
+  return battery_level_uuid;
+}
+
+}  // namespace
+
+namespace ash {
+
+class GattBatteryPercentageFetcherTest : public testing::Test {
+ protected:
+  GattBatteryPercentageFetcherTest() = default;
+
+  ~GattBatteryPercentageFetcherTest() override = default;
+
+  void SetUp() override {
+    mock_adapter_ =
+        base::MakeRefCounted<NiceMock<device::MockBluetoothAdapter>>();
+
+    // By default, |mock_device_| is paired, connected, with Gatt Services
+    // Discovery completed and returns |mock_service_| when requested for
+    // for available services. These behaviors can be overridden in tests.
+    mock_device_ = std::make_unique<NiceMock<device::MockBluetoothDevice>>(
+        mock_adapter_.get(), 0 /* bluetooth_class */, "device_name",
+        kDeviceAddress, true /* paired */, true /* connected */);
+    ON_CALL(*mock_adapter_, GetDevice(kDeviceAddress))
+        .WillByDefault(Return(mock_device_.get()));
+    ON_CALL(*mock_device_, IsGattServicesDiscoveryComplete())
+        .WillByDefault(Return(true));
+    ASSERT_FALSE(mock_device_->battery_percentage());
+
+    // By default, |mock_service_| returns a vector containing
+    // |mock_characteristic_| when requested for the battery level
+    // characteristic. This behavior can be overridden in tests.
+    mock_service_ =
+        std::make_unique<NiceMock<device::MockBluetoothGattService>>(
+            mock_device_.get(), kServiceID, GetBatteryServiceUUID(),
+            true /* is_primary */, false /* is_local */);
+    std::vector<device::BluetoothRemoteGattService*> services = {
+        mock_service_.get()};
+    ON_CALL(*mock_device_, GetGattServices()).WillByDefault(Return(services));
+
+    mock_characteristic_ =
+        std::make_unique<NiceMock<device::MockBluetoothGattCharacteristic>>(
+            mock_service_.get(), kCharacteristicID, GetBatteryLevelUUID(),
+            false /* is_local */,
+            BluetoothRemoteGattCharacteristic::PROPERTY_READ,
+            BluetoothRemoteGattCharacteristic::PERMISSION_READ);
+    std::vector<BluetoothRemoteGattCharacteristic*> characteristics = {
+        mock_characteristic_.get()};
+    ON_CALL(*mock_service_, GetCharacteristicsByUUID(GetBatteryLevelUUID()))
+        .WillByDefault(Return(characteristics));
+
+    device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);
+
+    // Create a GattBatteryPercentageFetcher.
+    ExpectGattConnection();
+    fetcher_ = GattBatteryPercentageFetcher::Factory::NewInstance(
+        mock_adapter_, kDeviceAddress,
+        base::BindOnce(
+            &GattBatteryPercentageFetcherTest::OnBatteryPercentageFetched,
+            base::Unretained(this)));
+  }
+
+  void ExpectGattConnection() {
+    EXPECT_CALL(*mock_device_, CreateGattConnection(_, _))
+        .WillOnce(DoAll(SaveArg<0>(&create_gatt_connection_success_callback_),
+                        SaveArg<1>(&create_gatt_connection_error_callback_)));
+  }
+
+  void ExpectReadCharacteristic() {
+    EXPECT_CALL(*mock_characteristic_, ReadRemoteCharacteristic_(_, _))
+        .WillOnce(
+            DoAll(MoveArg<0>(&read_remote_characteristic_callback_),
+                  MoveArg<1>(&read_remote_characteristic_error_callback_)));
+  }
+
+  void OnBatteryPercentageFetched(base::Optional<uint8_t> battery_percentage) {
+    battery_percentage_ = battery_percentage;
+    battery_percentage_callback_called_ = true;
+  }
+
+  void VerifyFetchResult(base::Optional<uint8_t> expected_result) {
+    EXPECT_TRUE(battery_percentage_callback_called_);
+    EXPECT_EQ(expected_result, battery_percentage_);
+  }
+
+  std::unique_ptr<GattBatteryPercentageFetcher> fetcher_;
+
+  base::Optional<uint8_t> battery_percentage_;
+  bool battery_percentage_callback_called_ = false;
+
+  scoped_refptr<NiceMock<device::MockBluetoothAdapter>> mock_adapter_;
+  std::unique_ptr<device::MockBluetoothDevice> mock_device_;
+
+  std::unique_ptr<device::MockBluetoothGattService> mock_service_;
+  std::unique_ptr<device::MockBluetoothGattCharacteristic> mock_characteristic_;
+
+  BluetoothDevice::GattConnectionCallback
+      create_gatt_connection_success_callback_;
+  BluetoothDevice::ConnectErrorCallback create_gatt_connection_error_callback_;
+  BluetoothRemoteGattCharacteristic::ValueCallback
+      read_remote_characteristic_callback_;
+  BluetoothRemoteGattCharacteristic::ErrorCallback
+      read_remote_characteristic_error_callback_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(GattBatteryPercentageFetcherTest);
+};
+
+TEST_F(GattBatteryPercentageFetcherTest,
+       ReadBattery_GattServicesDiscoveredOnGattConnection) {
+  ExpectReadCharacteristic();
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+
+  std::move(read_remote_characteristic_callback_).Run({kBatteryPercentage});
+  VerifyFetchResult(kBatteryPercentage);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest,
+       ReadBattery_GattServicesDiscoveredAfterGattConnection) {
+  ExpectReadCharacteristic();
+  ON_CALL(*mock_device_, IsGattServicesDiscoveryComplete())
+      .WillByDefault(Return(false));
+
+  // GattServicesDiscoveryComplete() should not have run yet.
+  EXPECT_TRUE(read_remote_characteristic_error_callback_.is_null());
+  EXPECT_TRUE(read_remote_characteristic_callback_.is_null());
+
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+
+  mock_adapter_->NotifyGattServicesDiscovered(mock_device_.get());
+
+  // GattServicesDiscoveryComplete() should have run.
+  std::move(read_remote_characteristic_callback_).Run({kBatteryPercentage});
+  VerifyFetchResult(kBatteryPercentage);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest,
+       IgnoreGattServicesDiscoveredBeforeGattConnection) {
+  // Case where the GATT connection has not been established yet, but the
+  // services are done with discovery. Should not try reading the battery level.
+  EXPECT_CALL(*mock_characteristic_, ReadRemoteCharacteristic_(_, _)).Times(0);
+  mock_adapter_->NotifyGattServicesDiscovered(mock_device_.get());
+}
+
+TEST_F(GattBatteryPercentageFetcherTest, ErrorOpeningGattConnection) {
+  create_gatt_connection_error_callback_.Run(
+      BluetoothDevice::ERROR_AUTH_TIMEOUT);
+  VerifyFetchResult(base::nullopt /* expected_result */);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest, BatteryServiceUnavailable) {
+  ON_CALL(*mock_device_, GetGattServices())
+      .WillByDefault(
+          Return(std::vector<device::BluetoothRemoteGattService*>()));
+
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+  VerifyFetchResult(base::nullopt /* expected_result */);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest, MissingBatteryLevelCharacteristic) {
+  ON_CALL(*mock_service_, GetCharacteristicsByUUID(GetBatteryLevelUUID()))
+      .WillByDefault(Return(std::vector<BluetoothRemoteGattCharacteristic*>()));
+  EXPECT_CALL(*mock_characteristic_, ReadRemoteCharacteristic_(_, _)).Times(0);
+
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+  VerifyFetchResult(base::nullopt /* expected_result */);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest, ErrorReadingRemoteCharacteristic) {
+  ExpectReadCharacteristic();
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+
+  std::move(read_remote_characteristic_error_callback_)
+      .Run(device::BluetoothGattService::GATT_ERROR_UNKNOWN);
+  VerifyFetchResult(base::nullopt /* expected_result */);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest,
+       BadFormatForBatteryLevelValue_MadeOfMultipleBytes) {
+  ExpectReadCharacteristic();
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+
+  // Battery value made of a multibyte vector.
+  std::move(read_remote_characteristic_callback_)
+      .Run({kBatteryPercentage, kBatteryPercentage});
+  VerifyFetchResult(base::nullopt /* expected_result */);
+}
+
+TEST_F(GattBatteryPercentageFetcherTest,
+       BadFormatForBatteryLevelValue_ValueAbove100Percent) {
+  ExpectReadCharacteristic();
+  create_gatt_connection_success_callback_.Run(
+      std::make_unique<NiceMock<device::MockBluetoothGattConnection>>(
+          mock_adapter_, kDeviceAddress));
+
+  uint8_t new_battery_percentage = 101;
+  std::move(read_remote_characteristic_callback_).Run({new_battery_percentage});
+  VerifyFetchResult(base::nullopt /* expected_result */);
+}
+
+}  // namespace ash
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index d732c83..8150a75 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -189,8 +189,6 @@
     "system_menu_tracing.icon",
     "system_menu_timer.icon",
     "system_menu_new_user.icon",
-    "system_menu_night_light_off.icon",
-    "system_menu_night_light_on.icon",
     "system_menu_usb.icon",
     "system_menu_videocam.icon",
     "system_menu_volume_high.icon",
@@ -207,7 +205,6 @@
     "system_tray_do_not_disturb.icon",
     "system_tray_family_link.icon",
     "system_tray_managed.icon",
-    "system_tray_night_light.icon",
     "system_tray_notification_counter_plus.icon",
     "system_tray_recording.icon",
     "system_tray_rotation_lock_auto.icon",
diff --git a/ash/resources/vector_icons/system_menu_night_light_off.icon b/ash/resources/vector_icons/system_menu_night_light_off.icon
deleted file mode 100644
index cb618cec..0000000
--- a/ash/resources/vector_icons/system_menu_night_light_off.icon
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 40,
-MOVE_TO, 34.3f, 27.18f,
-LINE_TO, 18.38f, 11.26f,
-CUBIC_TO, 19.89f, 9.6f, 21.82f, 8.33f, 24, 7.63f,
-CUBIC_TO, 22.74f, 7.22f, 21.4f, 7, 20, 7,
-R_CUBIC_TO, -1.74f, 0, -3.4f, 0.34f, -4.92f, 0.96f,
-LINE_TO, 12.82f, 5.7f,
-CUBIC_TO, 14.98f, 4.61f, 17.42f, 4, 20, 4,
-R_CUBIC_TO, 8.83f, 0, 16, 7.17f, 16, 16,
-R_CUBIC_TO, 0, 2.58f, -0.61f, 5.02f, -1.7f, 7.18f,
-CLOSE,
-R_MOVE_TO, -3.56f, 4.68f,
-CUBIC_TO, 27.9f, 34.43f, 24.13f, 36, 20, 36,
-R_CUBIC_TO, -8.83f, 0, -16, -7.17f, -16, -16,
-R_CUBIC_TO, 0, -4.13f, 1.57f, -7.9f, 4.14f, -10.74f,
-R_LINE_TO, 2.12f, 2.12f,
-CUBIC_TO, 8.23f, 13.68f, 7, 16.7f, 7, 20,
-R_CUBIC_TO, 0, 7.18f, 5.82f, 13, 13, 13,
-R_CUBIC_TO, 1.4f, 0, 2.74f, -0.22f, 4, -0.63f,
-R_CUBIC_TO, -5.22f, -1.69f, -9, -6.59f, -9, -12.37f,
-R_CUBIC_TO, 0, -1.18f, 0.16f, -2.33f, 0.46f, -3.42f,
-LINE_TO, 30.74f, 31.86f,
-CLOSE,
-MOVE_TO, 5, 6.12f,
-LINE_TO, 7.12f, 4,
-R_LINE_TO, 28.63f, 28.63f,
-R_LINE_TO, -2.12f, 2.12f,
-LINE_TO, 5, 6.12f,
-CLOSE
-
-CANVAS_DIMENSIONS, 20,
-MOVE_TO, 17.15f, 13.59f,
-LINE_TO, 9.44f, 5.88f,
-R_CUBIC_TO, 0.7f, -0.72f, 1.57f, -1.27f, 2.56f, -1.59f,
-R_CUBIC_TO, -0.59f, -0.19f, -1.23f, -0.29f, -1.88f, -0.29f,
-R_CUBIC_TO, -0.76f, 0, -1.49f, 0.14f, -2.17f, 0.39f,
-LINE_TO, 6.41f, 2.85f,
-CUBIC_TO, 7.49f, 2.31f, 8.71f, 2, 10, 2,
-R_CUBIC_TO, 4.42f, 0, 8, 3.58f, 8, 8,
-R_CUBIC_TO, 0, 1.29f, -0.31f, 2.51f, -0.85f, 3.59f,
-CLOSE,
-R_MOVE_TO, -1.78f, 2.34f,
-CUBIC_TO, 13.95f, 17.22f, 12.06f, 18, 10, 18,
-R_CUBIC_TO, -4.42f, 0, -8, -3.58f, -8, -8,
-R_CUBIC_TO, 0, -2.06f, 0.78f, -3.95f, 2.07f, -5.37f,
-R_LINE_TO, 1.43f, 1.43f,
-CUBIC_TO, 4.57f, 7.12f, 4, 8.49f, 4, 10,
-R_CUBIC_TO, 0, 3.31f, 2.74f, 6, 6.12f, 6,
-R_CUBIC_TO, 0.66f, 0, 1.29f, -0.1f, 1.88f, -0.29f,
-R_CUBIC_TO, -2.46f, -0.78f, -4.24f, -3.04f, -4.24f, -5.71f,
-R_CUBIC_TO, 0, -0.51f, 0.07f, -1.01f, 0.19f, -1.49f,
-R_LINE_TO, 7.41f, 7.42f,
-CLOSE,
-MOVE_TO, 2.5f, 3.06f,
-LINE_TO, 3.56f, 2,
-R_LINE_TO, 14.31f, 14.31f,
-R_LINE_TO, -1.06f, 1.06f,
-LINE_TO, 2.5f, 3.06f,
-CLOSE
diff --git a/ash/resources/vector_icons/system_menu_night_light_on.icon b/ash/resources/vector_icons/system_menu_night_light_on.icon
deleted file mode 100644
index 442834d..0000000
--- a/ash/resources/vector_icons/system_menu_night_light_on.icon
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 40,
-MOVE_TO, 20, 4,
-R_CUBIC_TO, 8.83f, 0, 16, 7.17f, 16, 16,
-R_CUBIC_TO, 0, 8.83f, -7.17f, 16, -16, 16,
-CUBIC_TO_SHORTHAND, 4, 28.83f, 4, 20,
-CUBIC_TO_SHORTHAND, 11.17f, 4, 20, 4,
-CLOSE,
-R_MOVE_TO, 4, 3.63f,
-CUBIC_TO, 22.74f, 7.22f, 21.4f, 7, 20, 7,
-CUBIC_TO, 12.82f, 7, 7, 12.82f, 7, 20,
-R_CUBIC_TO, 0, 7.18f, 5.82f, 13, 13, 13,
-R_CUBIC_TO, 1.4f, 0, 2.74f, -0.22f, 4, -0.63f,
-R_CUBIC_TO, -5.22f, -1.69f, -9, -6.59f, -9, -12.37f,
-R_CUBIC_TO, 0, -5.78f, 3.78f, -10.69f, 9, -12.37f,
-CLOSE
-
-CANVAS_DIMENSIONS, 20,
-MOVE_TO, 10, 2,
-R_CUBIC_TO, 4.42f, 0, 8, 3.58f, 8, 8,
-R_CUBIC_TO, 0, 4.42f, -3.58f, 8, -8, 8,
-R_CUBIC_TO, -4.42f, 0, -8, -3.58f, -8, -8,
-R_CUBIC_TO, 0, -4.42f, 3.58f, -8, 8, -8,
-CLOSE,
-R_MOVE_TO, -0.65f, 2,
-CUBIC_TO, 6.4f, 4, 4, 6.69f, 4, 10,
-R_CUBIC_TO, 0, 3.31f, 2.4f, 6, 5.35f, 6,
-R_CUBIC_TO, 0.57f, 0, 1.13f, -0.1f, 1.65f, -0.29f,
-CUBIC_TO, 8.85f, 14.93f, 7.29f, 12.67f, 7.29f, 10,
-R_CUBIC_TO, 0, -2.67f, 1.56f, -4.93f, 3.71f, -5.71f,
-CUBIC_TO, 10.48f, 4.1f, 9.93f, 4, 9.35f, 4,
-CLOSE
diff --git a/ash/resources/vector_icons/system_tray_night_light.icon b/ash/resources/vector_icons/system_tray_night_light.icon
deleted file mode 100644
index 236d22a..0000000
--- a/ash/resources/vector_icons/system_tray_night_light.icon
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 32,
-MOVE_TO, 16, 3,
-R_CUBIC_TO, 7.18f, 0, 13, 5.82f, 13, 13,
-R_CUBIC_TO, 0, 7.18f, -5.82f, 13, -13, 13,
-CUBIC_TO_SHORTHAND, 3, 23.18f, 3, 16,
-CUBIC_TO_SHORTHAND, 8.82f, 3, 16, 3,
-CLOSE,
-R_MOVE_TO, 3, 2.53f,
-CUBIC_TO, 17.96f, 5.19f, 16.86f, 5, 15.71f, 5,
-CUBIC_TO, 9.79f, 5, 5, 9.93f, 5, 16,
-R_CUBIC_TO, 0, 6.07f, 4.79f, 11, 10.71f, 11,
-R_CUBIC_TO, 1.15f, 0, 2.26f, -0.19f, 3.29f, -0.53f,
-R_CUBIC_TO, -4.3f, -1.43f, -7.41f, -5.58f, -7.41f, -10.47f,
-R_CUBIC_TO, 0, -4.89f, 3.11f, -9.04f, 7.41f, -10.47f,
-CLOSE
-
-CANVAS_DIMENSIONS, 16,
-MOVE_TO, 7, 0,
-CUBIC_TO, 10.86f, 0, 14, 3.14f, 14, 7,
-CUBIC_TO, 14, 10.86f, 10.86f, 14, 7, 14,
-CUBIC_TO, 3.14f, 14, 0, 10.86f, 0, 7,
-CUBIC_TO, 0, 3.14f, 3.14f, 0, 7, 0,
-CLOSE,
-MOVE_TO, 9, 1.29f,
-CUBIC_TO, 8.41f, 1.1f, 7.77f, 1, 7.12f, 1,
-CUBIC_TO, 3.74f, 1, 1, 3.69f, 1, 7,
-CUBIC_TO, 1, 10.31f, 3.74f, 13, 7.12f, 13,
-CUBIC_TO, 7.77f, 13, 8.41f, 12.9f, 9, 12.71f,
-CUBIC_TO, 6.54f, 11.93f, 4.76f, 9.67f, 4.76f, 7,
-CUBIC_TO, 4.76f, 4.33f, 6.54f, 2.07f, 9, 1.29f,
-LINE_TO, 9, 1.29f,
-CLOSE
diff --git a/ash/shelf/kiosk_next_shelf_view.cc b/ash/shelf/kiosk_next_shelf_view.cc
deleted file mode 100644
index 46f25af..0000000
--- a/ash/shelf/kiosk_next_shelf_view.cc
+++ /dev/null
@@ -1,166 +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/shelf/kiosk_next_shelf_view.h"
-
-#include <memory>
-
-#include "ash/display/screen_orientation_controller.h"
-#include "ash/home_screen/home_screen_controller.h"
-#include "ash/public/cpp/shelf_model.h"
-#include "ash/shelf/back_button.h"
-#include "ash/shelf/home_button.h"
-#include "ash/shelf/overflow_button.h"
-#include "ash/shelf/shelf.h"
-#include "ash/shelf/shelf_button_delegate.h"
-#include "ash/shelf/shelf_constants.h"
-#include "ash/shelf/shelf_widget.h"
-#include "ash/shell.h"
-#include "ash/system/status_area_widget.h"
-#include "ash/wm/tablet_mode/tablet_mode_controller.h"
-#include "base/logging.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/geometry/insets.h"
-#include "ui/gfx/geometry/insets_f.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/geometry/size.h"
-#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
-#include "ui/views/animation/ink_drop_mask.h"
-#include "ui/views/animation/ink_drop_ripple.h"
-#include "ui/views/view.h"
-#include "ui/views/widget/widget.h"
-
-namespace ash {
-
-namespace {
-
-// Kiosk Next back button with permanent round rectangle background.
-class KioskNextBackButton : public BackButton {
- public:
-  explicit KioskNextBackButton(ShelfButtonDelegate* shelf_button_delegate)
-      : BackButton(shelf_button_delegate) {}
-  ~KioskNextBackButton() override = default;
-
- private:
-  // views::BackButton:
-  void PaintButtonContents(gfx::Canvas* canvas) override {
-    PaintBackground(canvas, GetContentsBounds());
-    BackButton::PaintButtonContents(canvas);
-  }
-
-  std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override {
-    return std::make_unique<views::RoundRectInkDropMask>(
-        size(), gfx::InsetsF(), kKioskNextShelfControlWidthDp / 2);
-  }
-
-  std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override {
-    return std::make_unique<views::FloodFillInkDropRipple>(
-        size(), gfx::Insets(), GetInkDropCenterBasedOnLastEvent(),
-        GetInkDropBaseColor(), ink_drop_visible_opacity());
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(KioskNextBackButton);
-};
-
-// Kiosk Next home button with permanent round rectangle background.
-class KioskNextHomeButton : public HomeButton {
- public:
-  explicit KioskNextHomeButton(ShelfButtonDelegate* shelf_button_delegate)
-      : HomeButton(shelf_button_delegate) {}
-  ~KioskNextHomeButton() override = default;
-
- private:
-  // views::HomeButton:
-  void PaintButtonContents(gfx::Canvas* canvas) override {
-    PaintBackground(canvas, GetContentsBounds());
-    HomeButton::PaintButtonContents(canvas);
-  }
-
-  std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override {
-    return std::make_unique<views::RoundRectInkDropMask>(
-        size(), gfx::InsetsF(), kKioskNextShelfControlWidthDp / 2);
-  }
-
-  std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override {
-    return std::make_unique<views::FloodFillInkDropRipple>(
-        size(), gfx::Insets(), GetInkDropCenterBasedOnLastEvent(),
-        GetInkDropBaseColor(), ink_drop_visible_opacity());
-  }
-
-  void OnPressed(app_list::AppListShowSource show_source,
-                 base::TimeTicks time_stamp) override {
-    Shell::Get()->home_screen_controller()->GoHome(GetDisplayId());
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(KioskNextHomeButton);
-};
-
-bool IsTabletModeEnabled() {
-  // This check is needed, because tablet mode controller is destroyed before
-  // shelf widget. See https://crbug.com/967149 for more details.
-  return Shell::Get()->tablet_mode_controller() &&
-         Shell::Get()->tablet_mode_controller()->InTabletMode();
-}
-
-}  // namespace
-
-KioskNextShelfView::KioskNextShelfView(ShelfModel* model,
-                                       Shelf* shelf,
-                                       ShelfWidget* shelf_widget)
-    : ShelfView(model, shelf, shelf_widget) {
-  // Kiosk next shelf has 2 navigation buttons (back and home), no app items
-  // in its shelf model, and it is expected to be used in tablet mode with
-  // bottom shelf alignment. It can be adapted to different requirements, but
-  // they should be explicitly verified first.
-  DCHECK(IsTabletModeEnabled());
-  DCHECK(shelf->IsHorizontalAlignment());
-  DCHECK_EQ(0, model->item_count());
-}
-
-KioskNextShelfView::~KioskNextShelfView() = default;
-
-void KioskNextShelfView::Init() {
-  ShelfView::Init();
-
-  // Needs to be called after base class call that creates below objects.
-  // TODO(agawronska): Separator and overflow button are not needed in Kiosk
-  // Next shelf. They should be moved to DefaultShelfView subclass and the below
-  // code should be removed.
-  DCHECK(overflow_button());
-  overflow_button()->SetVisible(false);
-}
-
-void KioskNextShelfView::CalculateIdealBounds() {
-  DCHECK(shelf()->IsHorizontalAlignment());
-  DCHECK_EQ(0, model()->item_count());
-  DCHECK_GE(ShelfConstants::shelf_size(), kKioskNextShelfControlHeightDp);
-
-  // TODO(https://crbug.com/965690): Button spacing might be relative to shelf
-  // width. Reevaluate this piece once visual spec is available.
-  const int control_buttons_spacing =
-      IsCurrentScreenOrientationLandscape()
-          ? kKioskNextShelfControlSpacingLandscapeDp
-          : kKioskNextShelfControlSpacingPortraitDp;
-  const int total_shelf_width =
-      shelf_widget()->GetWindowBoundsInScreen().width();
-  int x = total_shelf_width / 2 - kKioskNextShelfControlWidthDp -
-          control_buttons_spacing / 2;
-  int y = (ShelfConstants::shelf_size() - kKioskNextShelfControlHeightDp) / 2;
-
-  GetBackButton()->set_ideal_bounds(gfx::Rect(
-      x, y, kKioskNextShelfControlWidthDp, kKioskNextShelfControlHeightDp));
-  x += (kKioskNextShelfControlWidthDp + control_buttons_spacing);
-  GetHomeButton()->set_ideal_bounds(gfx::Rect(
-      x, y, kKioskNextShelfControlWidthDp, kKioskNextShelfControlHeightDp));
-}
-
-std::unique_ptr<BackButton> KioskNextShelfView::CreateBackButton() {
-  return std::make_unique<KioskNextBackButton>(this);
-}
-
-std::unique_ptr<HomeButton> KioskNextShelfView::CreateHomeButton() {
-  return std::make_unique<KioskNextHomeButton>(this);
-}
-
-}  // namespace ash
diff --git a/ash/shelf/kiosk_next_shelf_view.h b/ash/shelf/kiosk_next_shelf_view.h
deleted file mode 100644
index 31e9394..0000000
--- a/ash/shelf/kiosk_next_shelf_view.h
+++ /dev/null
@@ -1,46 +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_SHELF_KIOSK_NEXT_SHELF_VIEW_H_
-#define ASH_SHELF_KIOSK_NEXT_SHELF_VIEW_H_
-
-#include <memory>
-
-#include "ash/ash_export.h"
-#include "ash/shelf/shelf_view.h"
-#include "base/macros.h"
-
-namespace ash {
-
-class Shelf;
-class ShelfModel;
-class ShelfWidget;
-
-// Shelf view used in Kiosk Next mode.
-// This shelf view is much simpler than the default shelf view. It is always
-// bottom aligned. It shows centered control buttons (back and home). It does
-// not display apps' buttons. Because of this alignment overflow mode is not
-// needed. This shelf view is using ShelfModel, but it could be removed in the
-// future.
-class ASH_EXPORT KioskNextShelfView : public ShelfView {
- public:
-  KioskNextShelfView(ShelfModel* model,
-                     Shelf* shelf,
-                     ShelfWidget* shelf_widget);
-  ~KioskNextShelfView() override;
-
-  // All ShelfView overrides are public to keep them together.
-  // ShelfView:
-  void Init() override;
-  void CalculateIdealBounds() override;
-  std::unique_ptr<BackButton> CreateBackButton() override;
-  std::unique_ptr<HomeButton> CreateHomeButton() override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(KioskNextShelfView);
-};
-
-}  // namespace ash
-
-#endif  // ASH_SHELF_KIOSK_NEXT_SHELF_VIEW_H_
diff --git a/ash/shelf/shelf_constants.h b/ash/shelf/shelf_constants.h
index ac3847c..107dd4be 100644
--- a/ash/shelf/shelf_constants.h
+++ b/ash/shelf/shelf_constants.h
@@ -89,18 +89,6 @@
 constexpr float kShelfTooltipPreviewMaxRatio = 1.5;    // = 3/2
 constexpr float kShelfTooltipPreviewMinRatio = 0.666;  // = 2/3
 
-// Kiosk Next shelf constants.
-// TODO(agawronska): Make it a part of theme.
-
-// Size of the space between control buttons on the shelf. Changes within
-// orientation.
-constexpr int kKioskNextShelfControlSpacingPortraitDp = 96;
-constexpr int kKioskNextShelfControlSpacingLandscapeDp = 122;
-
-// Size of the shelf control buttons (back and home).
-constexpr int kKioskNextShelfControlWidthDp = 64;
-constexpr int kKioskNextShelfControlHeightDp = 40;
-
 class ShelfConstants {
  public:
   // Size of the shelf when visible (height when the shelf is horizontal and
diff --git a/ash/shelf/shelf_view_unittest.cc b/ash/shelf/shelf_view_unittest.cc
index 6bf23e5..ead6f15 100644
--- a/ash/shelf/shelf_view_unittest.cc
+++ b/ash/shelf/shelf_view_unittest.cc
@@ -15,10 +15,6 @@
 #include "ash/display/screen_orientation_controller_test_api.h"
 #include "ash/focus_cycler.h"
 #include "ash/ime/ime_controller.h"
-#include "ash/kiosk_next/kiosk_next_shell_controller_impl.h"
-#include "ash/kiosk_next/kiosk_next_shell_test_util.h"
-#include "ash/kiosk_next/mock_kiosk_next_shell_client.h"
-#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/shelf_item_delegate.h"
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/public/cpp/shelf_prefs.h"
@@ -3894,124 +3890,4 @@
   // This should have brought focus to the first element on the shelf.
   EXPECT_TRUE(shelf_view_->GetHomeButton()->HasFocus());
 }
-
-class KioskNextShelfViewTest : public ShelfViewTest {
- public:
-  KioskNextShelfViewTest() {
-    scoped_feature_list_.InitAndEnableFeature(features::kKioskNextShell);
-  }
-
-  void SetUp() override {
-    set_start_session(false);
-    ShelfViewTest::SetUp();
-    client_ = std::make_unique<MockKioskNextShellClient>();
-  }
-
-  void TearDown() override {
-    client_.reset();
-    ShelfViewTest::TearDown();
-  }
-
- protected:
-  void LogInKioskNextUserInternal() {
-    LogInKioskNextUser(GetSessionControllerClient());
-
-    // The shelf_view_ in ShelfWidget will be replaced. Therefore, we need
-    // to update |shelf_view_|.
-    shelf_view_ = GetPrimaryShelf()->GetShelfViewForTesting();
-    ASSERT_GE(shelf_view_->width(), 500);
-
-    test_api_.reset(new ShelfViewTestAPI(shelf_view_));
-    test_api_->SetAnimationDuration(1);  // Speeds up animation for test.
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-  std::unique_ptr<MockKioskNextShellClient> client_;
-
-  DISALLOW_COPY_AND_ASSIGN(KioskNextShelfViewTest);
-};
-
-TEST_F(KioskNextShelfViewTest, AppButtonHidden) {
-  // When a KioskNextUser is not logged in, the shelf model is not hosted
-  // in KioskNextSellController.
-  EXPECT_FALSE(shelf_view_->model() ==
-               Shell::Get()->kiosk_next_shell_controller()->shelf_model());
-
-  LogInKioskNextUserInternal();
-
-  // When a KiosknextUser is logged in, the shelf model for the shelf view
-  // is hosted in KioskNextShellController.
-  EXPECT_TRUE(shelf_view_->model() ==
-              Shell::Get()->kiosk_next_shell_controller()->shelf_model());
-
-  // The home and back buttons are always visible.
-  EXPECT_TRUE(shelf_view_->GetHomeButton()->GetVisible());
-  EXPECT_TRUE(shelf_view_->GetBackButton()->GetVisible());
-
-  ASSERT_FALSE(shelf_view_->GetOverflowButton()->GetVisible());
-  EXPECT_EQ(-1, shelf_view_->last_visible_index());
-}
-
-// Tests that control buttons (back/home) are positioned correctly in Kiosk Next
-// mode. They should be centered, but exact position depends on the screen
-// orientation.
-TEST_F(KioskNextShelfViewTest, ControlButtonsCentered) {
-  // Setup internal display, otherwise setting display rotation will not take
-  // any effect.
-  const int64_t display_id =
-      display::Screen::GetScreen()->GetPrimaryDisplay().id();
-  display::DisplayManager* display_manager = Shell::Get()->display_manager();
-  display::test::ScopedSetInternalDisplayId internal_display(display_manager,
-                                                             display_id);
-
-  ScreenOrientationControllerTestApi screen_orientation_test_api(
-      Shell::Get()->screen_orientation_controller());
-
-  LogInKioskNextUserInternal();
-
-  auto test_controls_bounds = [&](display::Display::Rotation rotation,
-                                  bool is_landscape) {
-    SCOPED_TRACE(base::StringPrintf("Rotation: %d", rotation));
-
-    screen_orientation_test_api.SetDisplayRotation(
-        rotation, display::Display::RotationSource::ACTIVE);
-
-    // Get shelf bounds from the widget - buttons should be centered
-    // relatively to the whole shelf area (consisting of shelf view and
-    // status area).
-    const gfx::Rect shelf_bounds = GetPrimaryShelf()
-                                       ->GetShelfViewForTesting()
-                                       ->GetWidget()
-                                       ->GetWindowBoundsInScreen();
-
-    // Switch to local shelf coordinates - buttons bounds are checked in
-    // relation to shelf.
-    gfx::Rect expected_button_area_bounds = gfx::Rect(shelf_bounds.size());
-    const gfx::Size expected_button_area_size =
-        gfx::Size(2 * kKioskNextShelfControlWidthDp +
-                      (is_landscape ? kKioskNextShelfControlSpacingLandscapeDp
-                                    : kKioskNextShelfControlSpacingPortraitDp),
-                  ShelfConstants::shelf_size());
-    expected_button_area_bounds.ClampToCenteredSize(expected_button_area_size);
-
-    const gfx::Rect back_button_bounds =
-        shelf_view_->GetBackButton()->ideal_bounds();
-    EXPECT_FALSE(back_button_bounds.IsEmpty());
-    EXPECT_TRUE(expected_button_area_bounds.Contains(back_button_bounds));
-
-    const gfx::Rect home_button_bounds =
-        shelf_view_->GetHomeButton()->ideal_bounds();
-    EXPECT_FALSE(home_button_bounds.IsEmpty());
-    EXPECT_TRUE(expected_button_area_bounds.Contains(home_button_bounds));
-
-    EXPECT_FALSE(back_button_bounds.Intersects(home_button_bounds));
-  };
-
-  test_controls_bounds(display::Display::ROTATE_0, true /*is_landscape*/);
-  test_controls_bounds(display::Display::ROTATE_90, false /*is_landscape*/);
-  test_controls_bounds(display::Display::ROTATE_180, true /*is_landscape*/);
-  test_controls_bounds(display::Display::ROTATE_270, false /*is_landscape*/);
-}
-
 }  // namespace ash
diff --git a/ash/shelf/shelf_widget.cc b/ash/shelf/shelf_widget.cc
index 644dfae..47e3e27 100644
--- a/ash/shelf/shelf_widget.cc
+++ b/ash/shelf/shelf_widget.cc
@@ -9,7 +9,6 @@
 #include "ash/animation/animation_change_type.h"
 #include "ash/focus_cycler.h"
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
-#include "ash/kiosk_next/kiosk_next_shell_controller_impl.h"
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/shelf_model.h"
@@ -18,7 +17,6 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shelf/default_shelf_view.h"
 #include "ash/shelf/home_button.h"
-#include "ash/shelf/kiosk_next_shelf_view.h"
 #include "ash/shelf/login_shelf_view.h"
 #include "ash/shelf/overflow_bubble.h"
 #include "ash/shelf/overflow_bubble_view.h"
@@ -345,14 +343,6 @@
 
   background_animator_.AddObserver(delegate_view_);
   shelf_->AddObserver(this);
-
-  // KioskNextShell controller may have already notified its observers that
-  // it has been enabled by the time this ShelfWidget is being created.
-  if (Shell::Get()->kiosk_next_shell_controller()->IsEnabled()) {
-    OnKioskNextEnabled();
-  } else {
-    Shell::Get()->kiosk_next_shell_controller()->AddObserver(this);
-  }
 }
 
 ShelfWidget::~ShelfWidget() {
@@ -380,9 +370,6 @@
   background_animator_.RemoveObserver(delegate_view_);
   shelf_->RemoveObserver(this);
 
-  if (Shell::Get()->kiosk_next_shell_controller())
-    Shell::Get()->kiosk_next_shell_controller()->RemoveObserver(this);
-
   // Don't need to observe focus/activation during shutdown.
   Shell::Get()->focus_cycler()->RemoveWidget(this);
   SetFocusCycler(nullptr);
@@ -577,18 +564,6 @@
   login_shelf_view_->UpdateAfterSessionChange();
 }
 
-void ShelfWidget::OnKioskNextEnabled() {
-  // Hide the shelf view and delete/remove it.
-  shelf_view_->SetVisible(false);
-  delete shelf_view_;
-
-  shelf_view_ = new KioskNextShelfView(
-      Shell::Get()->kiosk_next_shell_controller()->shelf_model(), shelf_, this);
-  shelf_view_->Init();
-  GetContentsView()->AddChildView(shelf_view_);
-  shelf_view_->SetVisible(true);
-}
-
 SkColor ShelfWidget::GetShelfBackgroundColor() const {
   return delegate_view_->GetShelfBackgroundColor();
 }
diff --git a/ash/shelf/shelf_widget.h b/ash/shelf/shelf_widget.h
index fd0ab4b..4ae412e 100644
--- a/ash/shelf/shelf_widget.h
+++ b/ash/shelf/shelf_widget.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "ash/ash_export.h"
-#include "ash/kiosk_next/kiosk_next_shell_observer.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/session/session_observer.h"
 #include "ash/shelf/shelf_background_animator.h"
@@ -38,8 +37,7 @@
 class ASH_EXPORT ShelfWidget : public views::Widget,
                                public ShelfLayoutManagerObserver,
                                public ShelfObserver,
-                               public SessionObserver,
-                               public KioskNextShellObserver {
+                               public SessionObserver {
  public:
   ShelfWidget(aura::Window* shelf_container, Shelf* shelf);
   ~ShelfWidget() override;
@@ -120,9 +118,6 @@
   void OnSessionStateChanged(session_manager::SessionState state) override;
   void OnUserSessionAdded(const AccountId& account_id) override;
 
-  // KioskNextShellObserver:
-  void OnKioskNextEnabled() override;
-
   SkColor GetShelfBackgroundColor() const;
   bool GetHitTestRects(aura::Window* target,
                        gfx::Rect* hit_test_rect_mouse,
diff --git a/ash/style/ash_color_provider.cc b/ash/style/ash_color_provider.cc
index f207157..2f7dbae 100644
--- a/ash/style/ash_color_provider.cc
+++ b/ash/style/ash_color_provider.cc
@@ -75,11 +75,11 @@
   SkColor light_color, dark_color;
   switch (type) {
     case BaseLayerType::kTransparentWithBlur:
-      light_color = SkColorSetA(SK_ColorWHITE, 0xC7);
-      dark_color = SkColorSetA(gfx::kGoogleGrey900, 0xC7);
+      light_color = SkColorSetA(SK_ColorWHITE, 0xBC);  // 74%
+      dark_color = SkColorSetA(gfx::kGoogleGrey900, 0xBC);
       break;
     case BaseLayerType::kTransparentWithoutBlur:
-      light_color = SkColorSetA(SK_ColorWHITE, 0xE6);
+      light_color = SkColorSetA(SK_ColorWHITE, 0xE6);  // 90%
       dark_color = SkColorSetA(gfx::kGoogleGrey900, 0xE6);
       break;
     case BaseLayerType::kOpaque:
@@ -94,16 +94,16 @@
   SkColor light_color, dark_color;
   switch (type) {
     case PlusOneLayerType::kHairLine:
-      light_color = SkColorSetA(SK_ColorBLACK, 0x24);
+      light_color = SkColorSetA(SK_ColorBLACK, 0x24);  // 14%
       dark_color = SkColorSetA(SK_ColorWHITE, 0x24);
       break;
     case PlusOneLayerType::kSeparator:
-      light_color = SkColorSetA(SK_ColorBLACK, 0x24);
+      light_color = SkColorSetA(SK_ColorBLACK, 0x24);  // 14%
       dark_color = SkColorSetA(SK_ColorWHITE, 0x24);
       break;
     case PlusOneLayerType::kInActive:
-      light_color = SkColorSetA(SK_ColorBLACK, 0x0D);
-      dark_color = SkColorSetA(SK_ColorWHITE, 0x1A);
+      light_color = SkColorSetA(SK_ColorBLACK, 0x0D);  // 5%
+      dark_color = SkColorSetA(SK_ColorWHITE, 0x1A);   // 10%
       break;
     case PlusOneLayerType::kActive:
       light_color = gfx::kGoogleBlue600;
diff --git a/ash/system/night_light/night_light_toggle_button.cc b/ash/system/night_light/night_light_toggle_button.cc
deleted file mode 100644
index 2679fc4..0000000
--- a/ash/system/night_light/night_light_toggle_button.cc
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/system/night_light/night_light_toggle_button.h"
-
-#include "ash/shell.h"
-#include "ash/strings/grit/ash_strings.h"
-#include "ash/system/night_light/night_light_controller_impl.h"
-#include "ash/system/tray/tray_constants.h"
-#include "ash/system/tray/tray_popup_item_style.h"
-#include "ui/accessibility/ax_enums.mojom.h"
-#include "ui/accessibility/ax_node_data.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/paint_vector_icon.h"
-#include "ui/gfx/vector_icon_types.h"
-
-namespace ash {
-
-namespace {
-
-gfx::ImageSkia GetNightLightNormalStateIcon(bool night_light_enabled) {
-  if (night_light_enabled)
-    return gfx::CreateVectorIcon(kSystemMenuNightLightOnIcon, kMenuIconColor);
-
-  // Use the same icon theme used for inactive items in the tray when
-  // NightLight is not active.
-  return gfx::CreateVectorIcon(kSystemMenuNightLightOffIcon,
-                               TrayPopupItemStyle::GetIconColor(
-                                   TrayPopupItemStyle::ColorStyle::INACTIVE));
-}
-
-gfx::ImageSkia GetNightLightDisabledStateIcon(bool night_light_enabled) {
-  return gfx::CreateVectorIcon(night_light_enabled
-                                   ? kSystemMenuNightLightOnIcon
-                                   : kSystemMenuNightLightOffIcon,
-                               kMenuIconColorDisabled);
-}
-
-base::string16 GetNightLightTooltipText(bool night_light_enabled) {
-  return l10n_util::GetStringFUTF16(
-      IDS_ASH_STATUS_TRAY_NIGHT_LIGHT,
-      l10n_util::GetStringUTF16(
-          night_light_enabled ? IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_ON_STATE
-                              : IDS_ASH_STATUS_TRAY_NIGHT_LIGHT_OFF_STATE));
-}
-
-}  // namespace
-
-NightLightToggleButton::NightLightToggleButton(views::ButtonListener* listener)
-    : SystemMenuButton(listener,
-                       kSystemMenuNightLightOffIcon,
-                       IDS_ASH_STATUS_TRAY_NIGHT_LIGHT) {
-  Update();
-}
-
-void NightLightToggleButton::Toggle() {
-  Shell::Get()->night_light_controller()->Toggle();
-  Update();
-  NotifyAccessibilityEvent(ax::mojom::Event::kAriaAttributeChanged, true);
-}
-
-const char* NightLightToggleButton::GetClassName() const {
-  return "NightLightToggleButton";
-}
-
-void NightLightToggleButton::Update() {
-  const bool night_light_enabled =
-      Shell::Get()->night_light_controller()->GetEnabled();
-
-  SetTooltipText(GetNightLightTooltipText(night_light_enabled));
-  SetImage(views::Button::STATE_NORMAL,
-           GetNightLightNormalStateIcon(night_light_enabled));
-  SetImage(views::Button::STATE_DISABLED,
-           GetNightLightDisabledStateIcon(night_light_enabled));
-}
-
-void NightLightToggleButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
-  const bool is_enabled = Shell::Get()->night_light_controller()->GetEnabled();
-  node_data->SetName(GetNightLightTooltipText(is_enabled));
-  node_data->role = ax::mojom::Role::kToggleButton;
-  node_data->SetCheckedState(is_enabled ? ax::mojom::CheckedState::kTrue
-                                        : ax::mojom::CheckedState::kFalse);
-}
-
-}  // namespace ash
diff --git a/ash/system/night_light/night_light_toggle_button.h b/ash/system/night_light/night_light_toggle_button.h
deleted file mode 100644
index 696e40c..0000000
--- a/ash/system/night_light/night_light_toggle_button.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_SYSTEM_NIGHT_LIGHT_NIGHT_LIGHT_TOGGLE_BUTTON_H_
-#define ASH_SYSTEM_NIGHT_LIGHT_NIGHT_LIGHT_TOGGLE_BUTTON_H_
-
-#include "ash/system/tray/system_menu_button.h"
-#include "base/macros.h"
-
-namespace ash {
-
-// The NightLight toggle button in the system tray.
-class NightLightToggleButton : public SystemMenuButton {
- public:
-  explicit NightLightToggleButton(views::ButtonListener* listener);
-  ~NightLightToggleButton() override = default;
-
-  // Toggles the status of NightLight.
-  void Toggle();
-
-  // views::View:
-  const char* GetClassName() const override;
-
- private:
-  // Updates the icon and its style based on the status of NightLight.
-  void Update();
-
-  // views::View:
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-
-  DISALLOW_COPY_AND_ASSIGN(NightLightToggleButton);
-};
-
-}  // namespace ash
-
-#endif  // ASH_SYSTEM_NIGHT_LIGHT_NIGHT_LIGHT_TOGGLE_BUTTON_H_
diff --git a/ash/system/power/power_status_view.cc b/ash/system/power/power_status_view.cc
deleted file mode 100644
index 0907825..0000000
--- a/ash/system/power/power_status_view.cc
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (c) 2012 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/system/power/power_status_view.h"
-
-#include "ash/strings/grit/ash_strings.h"
-#include "ash/system/power/tray_power.h"
-#include "ash/system/tray/tray_constants.h"
-#include "ash/system/tray/tray_popup_item_style.h"
-#include "ash/system/tray/tray_popup_utils.h"
-#include "base/strings/utf_string_conversions.h"
-#include "ui/accessibility/ax_node_data.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/geometry/insets.h"
-#include "ui/native_theme/native_theme.h"
-#include "ui/views/controls/image_view.h"
-#include "ui/views/controls/label.h"
-#include "ui/views/layout/box_layout.h"
-#include "ui/views/layout/grid_layout.h"
-
-namespace ash {
-
-PowerStatusView::PowerStatusView()
-    : percentage_label_(new views::Label),
-      separator_label_(new views::Label),
-      time_status_label_(new views::Label) {
-  SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
-
-  percentage_label_->SetEnabledColor(kHeaderTextColorNormal);
-  separator_label_->SetEnabledColor(kHeaderTextColorNormal);
-  separator_label_->SetText(base::ASCIIToUTF16(" - "));
-
-  SetLayoutManager(std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kHorizontal, gfx::Insets(0, 12),
-      kTrayPopupPaddingBetweenItems));
-
-  AddChildView(percentage_label_);
-  AddChildView(separator_label_);
-  AddChildView(time_status_label_);
-
-  PowerStatus::Get()->AddObserver(this);
-  OnPowerStatusChanged();
-}
-
-PowerStatusView::~PowerStatusView() {
-  PowerStatus::Get()->RemoveObserver(this);
-}
-
-void PowerStatusView::OnPowerStatusChanged() {
-  UpdateText();
-}
-
-void PowerStatusView::UpdateText() {
-  base::string16 battery_percentage;
-  base::string16 battery_time_status;
-
-  std::tie(battery_percentage, battery_time_status) =
-      PowerStatus::Get()->GetStatusStrings();
-
-  percentage_label_->SetVisible(!battery_percentage.empty());
-  percentage_label_->SetText(battery_percentage);
-  separator_label_->SetVisible(!battery_percentage.empty() &&
-                               !battery_time_status.empty());
-  time_status_label_->SetVisible(!battery_time_status.empty());
-  time_status_label_->SetText(battery_time_status);
-
-  accessible_name_ = PowerStatus::Get()->GetAccessibleNameString(true);
-
-  TrayPopupItemStyle style(TrayPopupItemStyle::FontStyle::SYSTEM_INFO);
-  style.SetupLabel(percentage_label_);
-  style.SetupLabel(separator_label_);
-  style.SetupLabel(time_status_label_);
-}
-
-void PowerStatusView::ChildPreferredSizeChanged(views::View* child) {
-  PreferredSizeChanged();
-}
-
-void PowerStatusView::Layout() {
-  views::View::Layout();
-
-  // Move the time_status_label_, separator_label_, and percentage_label_
-  // closer to each other.
-  if (percentage_label_ && separator_label_ && time_status_label_ &&
-      percentage_label_->GetVisible() && time_status_label_->GetVisible()) {
-    separator_label_->SetX(percentage_label_->bounds().right() + 1);
-    time_status_label_->SetX(separator_label_->bounds().right() + 1);
-  }
-}
-
-void PowerStatusView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
-  node_data->role = ax::mojom::Role::kLabelText;
-  node_data->SetName(accessible_name_);
-}
-
-const char* PowerStatusView::GetClassName() const {
-  return "PowerStatusView";
-}
-
-}  // namespace ash
diff --git a/ash/system/power/power_status_view.h b/ash/system/power/power_status_view.h
deleted file mode 100644
index c603576..0000000
--- a/ash/system/power/power_status_view.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (c) 2012 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_SYSTEM_POWER_POWER_STATUS_VIEW_H_
-#define ASH_SYSTEM_POWER_POWER_STATUS_VIEW_H_
-
-#include "ash/ash_export.h"
-#include "ash/system/power/power_status.h"
-#include "base/macros.h"
-#include "base/strings/string16.h"
-#include "ui/views/view.h"
-
-namespace views {
-class Label;
-}
-
-namespace ash {
-
-class ASH_EXPORT PowerStatusView : public views::View,
-                                   public PowerStatus::Observer {
- public:
-  PowerStatusView();
-  ~PowerStatusView() override;
-
-  // views::View:
-  void Layout() override;
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-  const char* GetClassName() const override;
-
-  // PowerStatus::Observer:
-  void OnPowerStatusChanged() override;
-
- private:
-  friend class PowerStatusViewTest;
-
-  void UpdateText();
-
-  // views::View:
-  void ChildPreferredSizeChanged(views::View* child) override;
-
-  views::Label* percentage_label_;
-  views::Label* separator_label_;
-  views::Label* time_status_label_;
-
-  base::string16 accessible_name_;
-
-  DISALLOW_COPY_AND_ASSIGN(PowerStatusView);
-};
-
-}  // namespace ash
-
-#endif  // ASH_SYSTEM_POWER_POWER_STATUS_VIEW_H_
diff --git a/ash/system/power/power_status_view_unittest.cc b/ash/system/power/power_status_view_unittest.cc
deleted file mode 100644
index c17889f9..0000000
--- a/ash/system/power/power_status_view_unittest.cc
+++ /dev/null
@@ -1,115 +0,0 @@
-// 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.
-
-#include "ash/system/power/power_status_view.h"
-
-#include "ash/strings/grit/ash_strings.h"
-#include "ash/system/power/power_status.h"
-#include "ash/test/ash_test_base.h"
-#include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/l10n/time_format.h"
-#include "ui/views/controls/label.h"
-
-using power_manager::PowerSupplyProperties;
-
-namespace ash {
-
-class PowerStatusViewTest : public AshTestBase {
- public:
-  PowerStatusViewTest() = default;
-  ~PowerStatusViewTest() override = default;
-
-  // Overridden from testing::Test:
-  void SetUp() override {
-    AshTestBase::SetUp();
-    view_.reset(new PowerStatusView());
-  }
-
-  void TearDown() override {
-    view_.reset();
-    AshTestBase::TearDown();
-  }
-
- protected:
-  void UpdatePowerStatus(const power_manager::PowerSupplyProperties& proto) {
-    PowerStatus::Get()->SetProtoForTesting(proto);
-    view_->OnPowerStatusChanged();
-  }
-
-  bool IsPercentageVisible() const {
-    return view_->percentage_label_->GetVisible();
-  }
-
-  bool IsTimeStatusVisible() const {
-    return view_->time_status_label_->GetVisible();
-  }
-
-  base::string16 RemainingTimeInView() const {
-    return view_->time_status_label_->GetText();
-  }
-
- private:
-  std::unique_ptr<PowerStatusView> view_;
-
-  DISALLOW_COPY_AND_ASSIGN(PowerStatusViewTest);
-};
-
-TEST_F(PowerStatusViewTest, Basic) {
-  EXPECT_FALSE(IsPercentageVisible());
-  EXPECT_TRUE(IsTimeStatusVisible());
-
-  // Disconnect the power.
-  PowerSupplyProperties prop;
-  prop.set_external_power(PowerSupplyProperties::DISCONNECTED);
-  prop.set_battery_state(PowerSupplyProperties::DISCHARGING);
-  prop.set_battery_percent(99.0);
-  prop.set_battery_time_to_empty_sec(120);
-  prop.set_is_calculating_battery_time(true);
-  UpdatePowerStatus(prop);
-
-  EXPECT_TRUE(IsPercentageVisible());
-  EXPECT_TRUE(IsTimeStatusVisible());
-  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING),
-            RemainingTimeInView());
-
-  prop.set_is_calculating_battery_time(false);
-  UpdatePowerStatus(prop);
-  EXPECT_NE(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING),
-            RemainingTimeInView());
-  EXPECT_NE(l10n_util::GetStringUTF16(
-                IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE),
-            RemainingTimeInView());
-
-  prop.set_external_power(PowerSupplyProperties::AC);
-  prop.set_battery_state(PowerSupplyProperties::CHARGING);
-  prop.set_battery_time_to_full_sec(120);
-  UpdatePowerStatus(prop);
-  EXPECT_TRUE(IsPercentageVisible());
-  EXPECT_TRUE(IsTimeStatusVisible());
-  EXPECT_NE(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING),
-            RemainingTimeInView());
-  EXPECT_NE(l10n_util::GetStringUTF16(
-                IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE),
-            RemainingTimeInView());
-
-  prop.set_external_power(PowerSupplyProperties::USB);
-  UpdatePowerStatus(prop);
-  EXPECT_TRUE(IsPercentageVisible());
-  EXPECT_TRUE(IsTimeStatusVisible());
-  EXPECT_EQ(l10n_util::GetStringUTF16(
-                IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE),
-            RemainingTimeInView());
-
-  // Tricky -- connected to non-USB but still discharging. Not likely happening
-  // on production though.
-  prop.set_external_power(PowerSupplyProperties::AC);
-  prop.set_battery_state(PowerSupplyProperties::DISCHARGING);
-  prop.set_battery_time_to_full_sec(120);
-  UpdatePowerStatus(prop);
-  EXPECT_TRUE(IsPercentageVisible());
-  EXPECT_FALSE(IsTimeStatusVisible());
-}
-
-}  // namespace ash
diff --git a/ash/system/tray/tray_constants.cc b/ash/system/tray/tray_constants.cc
index 7fc1444..36fadb7 100644
--- a/ash/system/tray/tray_constants.cc
+++ b/ash/system/tray/tray_constants.cc
@@ -27,7 +27,6 @@
 
 const int kTrayPopupAutoCloseDelayInSeconds = 2;
 const int kTrayPopupPaddingHorizontal = 18;
-const int kTrayPopupPaddingBetweenItems = 10;
 const int kTrayPopupButtonEndMargin = 10;
 const int kTrayPopupLabelHorizontalPadding = 4;
 const int kTrayPopupSliderHorizontalPadding = 16;
@@ -49,8 +48,6 @@
 
 const SkColor kHeaderBackgroundColor = SkColorSetRGB(0xf5, 0xf5, 0xf5);
 
-const SkColor kHeaderTextColorNormal = SkColorSetARGB(0x7f, 0, 0, 0);
-
 const SkColor kMobileNotConnectedXIconColor = SkColorSetRGB(0xb2, 0xb2, 0xb2);
 
 const SkColor kTrayIconColor = gfx::kGoogleGrey200;
diff --git a/ash/system/tray/tray_constants.h b/ash/system/tray/tray_constants.h
index e42301a..ad5e228 100644
--- a/ash/system/tray/tray_constants.h
+++ b/ash/system/tray/tray_constants.h
@@ -34,7 +34,6 @@
 
 extern const int kTrayPopupAutoCloseDelayInSeconds;
 extern const int kTrayPopupPaddingHorizontal;
-extern const int kTrayPopupPaddingBetweenItems;
 extern const int kTrayPopupButtonEndMargin;
 
 // The padding used on the left and right of labels. This applies to all labels
@@ -72,8 +71,6 @@
 
 extern const SkColor kHeaderBackgroundColor;
 
-extern const SkColor kHeaderTextColorNormal;
-
 // Constants for the title row.
 constexpr int kTitleRowVerticalPadding = 4;
 constexpr int kTitleRowProgressBarHeight = 2;
diff --git a/ash/system/tray/tray_popup_utils.cc b/ash/system/tray/tray_popup_utils.cc
index fc9fa38..8377a91 100644
--- a/ash/system/tray/tray_popup_utils.cc
+++ b/ash/system/tray/tray_popup_utils.cc
@@ -221,21 +221,6 @@
   view->layer()->SetFillsBoundsOpaquely(false);
 }
 
-void TrayPopupUtils::ShowStickyHeaderSeparator(views::View* view,
-                                               bool show_separator) {
-  if (show_separator) {
-    view->SetBorder(views::CreatePaddedBorder(
-        views::CreateSolidSidedBorder(0, 0, kTraySeparatorWidth, 0,
-                                      kMenuSeparatorColor),
-        gfx::Insets(kMenuSeparatorVerticalPadding, 0,
-                    kMenuSeparatorVerticalPadding - kTraySeparatorWidth, 0)));
-  } else {
-    view->SetBorder(views::CreateEmptyBorder(
-        gfx::Insets(kMenuSeparatorVerticalPadding, 0)));
-  }
-  view->SchedulePaint();
-}
-
 void TrayPopupUtils::ConfigureContainer(TriView::Container container,
                                         views::View* container_view) {
   container_view->SetLayoutManager(CreateDefaultLayoutManager(container));
@@ -348,14 +333,6 @@
   return separator;
 }
 
-views::Separator* TrayPopupUtils::CreateListSubHeaderSeparator() {
-  views::Separator* separator = new views::Separator();
-  separator->SetColor(kMenuSeparatorColor);
-  separator->SetBorder(views::CreateEmptyBorder(
-      kMenuSeparatorVerticalPadding - views::Separator::kThickness, 0, 0, 0));
-  return separator;
-}
-
 bool TrayPopupUtils::CanOpenWebUISettings() {
   return Shell::Get()->session_controller()->ShouldEnableSettings();
 }
diff --git a/ash/system/tray/tray_popup_utils.h b/ash/system/tray/tray_popup_utils.h
index 548f0d2..f422be6 100644
--- a/ash/system/tray/tray_popup_utils.h
+++ b/ash/system/tray/tray_popup_utils.h
@@ -125,9 +125,6 @@
   // Sets up |view| to be a sticky header in a tray detail scroll view.
   static void ConfigureAsStickyHeader(views::View* view);
 
-  // Configures a |view| to have a visible separator below.
-  static void ShowStickyHeaderSeparator(views::View* view, bool show_separator);
-
   // Configures |container_view| just like CreateDefaultRowView() would
   // configure |container| on its returned TriView. To be used when mutliple
   // targetable areas are required within a single row.
@@ -188,11 +185,6 @@
   // ownership of the returned separator.
   static views::Separator* CreateListItemSeparator(bool left_inset);
 
-  // Creates and returns a horizontal separator line to be drawn between rows
-  // in a detailed view above the sub-header rows. Caller assumes ownership of
-  // the returned separator.
-  static views::Separator* CreateListSubHeaderSeparator();
-
   // Returns true if it is possible to open WebUI settings in a browser window,
   // i.e. the user is logged in, not on the lock screen, not adding a secondary
   // user, and not in the supervised user creation flow.
diff --git a/ash/wm/overview/overview_controller.cc b/ash/wm/overview/overview_controller.cc
index b33aade..d7ccaa5 100644
--- a/ash/wm/overview/overview_controller.cc
+++ b/ash/wm/overview/overview_controller.cc
@@ -541,6 +541,10 @@
   // can accept text input and it resizes correctly with the a11y keyboard.
   keyboard::KeyboardUIController::Get()->HideKeyboardImplicitlyByUser();
 
+  // Prevent toggling overview during the split view divider snap animation.
+  if (Shell::Get()->split_view_controller()->IsDividerAnimating())
+    return true;
+
   auto windows =
       Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
 
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index 03595dd..3691ce3 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -555,6 +555,10 @@
     return (work_area_bounds_in_screen.height() - divider_size.height()) * 0.5f;
 }
 
+bool SplitViewController::IsDividerAnimating() {
+  return divider_snap_animation_ && divider_snap_animation_->is_animating();
+}
+
 void SplitViewController::StartResize(const gfx::Point& location_in_screen) {
   DCHECK(InSplitViewMode());
 
@@ -1442,10 +1446,6 @@
   return fix_position;
 }
 
-bool SplitViewController::IsDividerAnimating() {
-  return divider_snap_animation_ && divider_snap_animation_->is_animating();
-}
-
 void SplitViewController::StopAndShoveAnimatedDivider() {
   DCHECK(IsDividerAnimating());
 
diff --git a/ash/wm/splitview/split_view_controller.h b/ash/wm/splitview/split_view_controller.h
index 67bb129..a35e2fe 100644
--- a/ash/wm/splitview/split_view_controller.h
+++ b/ash/wm/splitview/split_view_controller.h
@@ -136,6 +136,9 @@
   // Gets the default value of |divider_position_|.
   int GetDefaultDividerPosition(aura::Window* window) const;
 
+  // Returns true during the divider snap animation.
+  bool IsDividerAnimating();
+
   void StartResize(const gfx::Point& location_in_screen);
   void Resize(const gfx::Point& location_in_screen);
   void EndResize(const gfx::Point& location_in_screen);
@@ -269,9 +272,6 @@
   // Returns the closest fix location for |divider_position_|.
   int GetClosestFixedDividerPosition();
 
-  // Returns true during the divider snap animation.
-  bool IsDividerAnimating();
-
   // While the divider is animating to somewhere, stop it and shove it there.
   void StopAndShoveAnimatedDivider();
 
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.cc b/ash/wm/tablet_mode/tablet_mode_controller.cc
index dea8fe5..dd70645 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller.cc
@@ -8,7 +8,6 @@
 #include <string>
 #include <utility>
 
-#include "ash/kiosk_next/kiosk_next_shell_controller_impl.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/fps_counter.h"
 #include "ash/public/cpp/shell_window_ids.h"
@@ -303,8 +302,6 @@
             base::Unretained(this)));
   }
 
-  Shell::Get()->kiosk_next_shell_controller()->AddObserver(this);
-
   chromeos::PowerManagerClient* power_manager_client =
       chromeos::PowerManagerClient::Get();
   power_manager_client->AddObserver(this);
@@ -332,7 +329,6 @@
                             tab_drag_in_splitview_count_);
 
   Shell::Get()->RemoveShellObserver(this);
-  Shell::Get()->kiosk_next_shell_controller()->RemoveObserver(this);
 
   if (ShouldInitTabletModeController()) {
     Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
@@ -618,11 +614,6 @@
   HandlePointingDeviceAddedOrRemoved();
 }
 
-void TabletModeController::OnKioskNextEnabled() {
-  tablet_mode_behavior_ = kLockInCurrentMode;
-  AttemptEnterTabletMode();
-}
-
 void TabletModeController::OnLayerAnimationStarted(
     ui::LayerAnimationSequence* sequence) {}
 
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.h b/ash/wm/tablet_mode/tablet_mode_controller.h
index 4c25a3c..ebd32ae 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.h
+++ b/ash/wm/tablet_mode/tablet_mode_controller.h
@@ -12,7 +12,6 @@
 #include "ash/ash_export.h"
 #include "ash/bluetooth_devices_observer.h"
 #include "ash/display/window_tree_host_manager.h"
-#include "ash/kiosk_next/kiosk_next_shell_observer.h"
 #include "ash/public/cpp/tablet_mode.h"
 #include "ash/session/session_observer.h"
 #include "ash/shell_observer.h"
@@ -71,7 +70,6 @@
       public WindowTreeHostManager::Observer,
       public SessionObserver,
       public ui::InputDeviceEventObserver,
-      public KioskNextShellObserver,
       public ui::LayerAnimationObserver {
  public:
   // Enable or disable using a screenshot for testing as it makes the
@@ -149,9 +147,6 @@
   void OnInputDeviceConfigurationChanged(uint8_t input_device_types) override;
   void OnDeviceListsComplete() override;
 
-  // KioskNextShellObserver:
-  void OnKioskNextEnabled() override;
-
   // ui::LayerAnimationObserver:
   void OnLayerAnimationStarted(ui::LayerAnimationSequence* sequence) override;
   void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override;
diff --git a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
index 4a95c375..51ce305 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
@@ -1483,35 +1483,6 @@
   EXPECT_TRUE(Shell::Get()->overview_controller()->StartOverview());
 }
 
-// Test that when OnKioskNextEnabled() is called the UI mode changes into
-// TabletMode. Ensure that UI mode keeps staying in Tablet Mode.
-TEST_F(TabletModeControllerTest, TestKioskNextModeUI) {
-  ui::DeviceDataManagerTestApi().SetMouseDevices({});
-  ui::DeviceDataManagerTestApi().SetTouchpadDevices({});
-  ui::DeviceDataManagerTestApi().SetKeyboardDevices({});
-
-  tablet_mode_controller()->OnKioskNextEnabled();
-  EXPECT_TRUE(IsTabletModeStarted());
-
-  // Attach a mouse. Check that we are still in Tablet Mode.
-  ui::DeviceDataManagerTestApi().SetMouseDevices(
-      {ui::InputDevice(0, ui::InputDeviceType::INPUT_DEVICE_USB, "mouse")});
-  EXPECT_TRUE(IsTabletModeStarted());
-  ui::DeviceDataManagerTestApi().SetMouseDevices({});
-
-  // Attach Touchpad
-  ui::DeviceDataManagerTestApi().SetTouchpadDevices(
-      {ui::InputDevice(1, ui::InputDeviceType::INPUT_DEVICE_USB, "touchpad")});
-  EXPECT_TRUE(IsTabletModeStarted());
-  ui::DeviceDataManagerTestApi().SetTouchpadDevices({});
-
-  // Attach Keyboard
-  ui::DeviceDataManagerTestApi().SetKeyboardDevices(
-      {ui::InputDevice(2, ui::InputDeviceType::INPUT_DEVICE_USB, "keyboard")});
-  EXPECT_TRUE(IsTabletModeStarted());
-  ui::DeviceDataManagerTestApi().SetKeyboardDevices({});
-}
-
 // Test that tablet mode controller does not respond to the input device changes
 // during its suspend.
 TEST_F(TabletModeControllerTest, DoNotObserverInputDeviceChangeDuringSuspend) {
diff --git a/base/android/java/src/org/chromium/base/library_loader/Linker.java b/base/android/java/src/org/chromium/base/library_loader/Linker.java
index af2b5600..c3388192 100644
--- a/base/android/java/src/org/chromium/base/library_loader/Linker.java
+++ b/base/android/java/src/org/chromium/base/library_loader/Linker.java
@@ -269,7 +269,7 @@
         assertLinkerTestsAreEnabled();
 
         synchronized (sLock) {
-            sSingleton.mTestRunnerClassName = testRunnerClassName;
+            Linker.getInstance().mTestRunnerClassName = testRunnerClassName;
         }
     }
 
diff --git a/base/android/jni_generator/jni_refactorer.py b/base/android/jni_generator/jni_refactorer.py
index de0da86..8502207 100755
--- a/base/android/jni_generator/jni_refactorer.py
+++ b/base/android/jni_generator/jni_refactorer.py
@@ -138,7 +138,7 @@
     else:
       import_insert = match.end() + 1
 
-  return "%s%s\n%s" % (contents[:import_insert], JCALLER_IMPORT_STRING,
+  return "%s%s\n%s" % (contents[:import_insert], import_string,
                        contents[import_insert:])
 
 
@@ -370,6 +370,10 @@
       ' instead of converting static methods to new jni.')
   arg_parser.add_argument(
       '--verbose', default=False, action='store_true', help='')
+  arg_parser.add_argument(
+      '--ignored-paths',
+      action='append',
+      help='Paths to ignore during conversion.')
 
   args = arg_parser.parse_args()
 
@@ -383,29 +387,21 @@
       java_file_paths = pickle.load(file)
       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'
-    ]
-
-    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')])
-
-      getPaths()
+    for root, _, files in os.walk(os.path.abspath('.')):
+      java_file_paths.extend(
+          ['%s/%s' % (root, f) for f in files if f.endswith('.java')])
 
   else:
     # Get all java files in current dir.
     java_file_paths = filter(lambda x: x.endswith('.java'),
                              map(os.path.abspath, os.listdir('.')))
 
+  if args.ignored_paths:
+    java_file_paths = [
+        path for path in java_file_paths
+        if all(p not in path for p in args.ignored_paths)
+    ]
+
   if args.cache:
     with open(PICKLE_LOCATION, 'w') as file:
       pickle.dump(filter_files_with_natives(java_file_paths), file)
diff --git a/base/mac/mach_port_rendezvous.cc b/base/mac/mach_port_rendezvous.cc
index 5e71440..43e5806 100644
--- a/base/mac/mach_port_rendezvous.cc
+++ b/base/mac/mach_port_rendezvous.cc
@@ -258,11 +258,15 @@
 
 // static
 MachPortRendezvousClient* MachPortRendezvousClient::GetInstance() {
-  static MachPortRendezvousClient* client = new MachPortRendezvousClient();
-  if (!client->did_acquire_ports()) {
-    bool ok = client->AcquirePorts();
-    DCHECK(ok);
+  static MachPortRendezvousClient* client = []() -> auto* {
+    auto* client = new MachPortRendezvousClient();
+    if (!client->AcquirePorts()) {
+      delete client;
+      client = nullptr;
+    }
+    return client;
   }
+  ();
   return client;
 }
 
@@ -296,8 +300,6 @@
 bool MachPortRendezvousClient::AcquirePorts() {
   AutoLock lock(lock_);
 
-  did_acquire_ports_ = true;
-
   mac::ScopedMachSendRight server_port;
   std::string bootstrap_name = GetBootstrapName();
   kern_return_t kr = bootstrap_look_up(
diff --git a/base/mac/mach_port_rendezvous.h b/base/mac/mach_port_rendezvous.h
index 422b2df..5fd9d08 100644
--- a/base/mac/mach_port_rendezvous.h
+++ b/base/mac/mach_port_rendezvous.h
@@ -82,7 +82,9 @@
  public:
   // Returns the instance of the server. Upon the first call to this method,
   // the server is created, which registers an endpoint in the Mach bootstrap
-  // namespace.
+  // namespace. If the rendezvous fails, which can happen if the server is not
+  // available, this returns null. Acquiring zero ports from the exchange is
+  // not considered a failure.
   static MachPortRendezvousServer* GetInstance();
 
   // Registers a collection of Mach ports |ports| to be acquirable by the
@@ -202,13 +204,8 @@
   // MachRendezvousPort with MACH_PORT_NULL is returned.
   MachRendezvousPort PortForKey(MachPortsForRendezvous::key_type key);
 
-  bool did_acquire_ports() { return did_acquire_ports_; }
-
   // Lock for the below data members.
   Lock lock_;
-  // Flag for if the client has attempted to acquire ports. If the client
-  // experienced an error in doing so, this will still be true.
-  bool did_acquire_ports_ = false;
   // The collection of ports that was acquired.
   MachPortsForRendezvous ports_;
 
diff --git a/base/mac/mach_port_rendezvous_unittest.cc b/base/mac/mach_port_rendezvous_unittest.cc
index 5af2a86..a66c1ce 100644
--- a/base/mac/mach_port_rendezvous_unittest.cc
+++ b/base/mac/mach_port_rendezvous_unittest.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/at_exit.h"
+#include "base/mac/foundation_util.h"
 #include "base/mac/mach_logging.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/multiprocess_test.h"
@@ -206,4 +207,25 @@
   }
 }
 
+MULTIPROCESS_TEST_MAIN(FailToRendezvous) {
+  // The rendezvous system uses the BaseBundleID to construct the bootstrap
+  // server name, so changing it will result in a failure to look it up.
+  base::mac::SetBaseBundleID("org.chromium.totallyfake");
+  CHECK_EQ(nullptr, base::MachPortRendezvousClient::GetInstance());
+  return 0;
+}
+
+TEST_F(MachPortRendezvousServerTest, FailToRendezvous) {
+  auto* server = MachPortRendezvousServer::GetInstance();
+  ASSERT_TRUE(server);
+
+  Process child = SpawnChild("FailToRendezvous");
+
+  int exit_code;
+  ASSERT_TRUE(WaitForMultiprocessTestChildExit(
+      child, TestTimeouts::action_timeout(), &exit_code));
+
+  EXPECT_EQ(0, exit_code);
+}
+
 }  // namespace base
diff --git a/base/memory/shared_memory.h b/base/memory/shared_memory.h
index bd97f11b..f095516 100644
--- a/base/memory/shared_memory.h
+++ b/base/memory/shared_memory.h
@@ -98,9 +98,6 @@
   // Closes a shared memory handle.
   static void CloseHandle(const SharedMemoryHandle& handle);
 
-  // Returns the maximum number of handles that can be open at once per process.
-  static size_t GetHandleLimit();
-
   // Duplicates The underlying OS primitive. Returns an invalid handle on
   // failure. The caller is responsible for destroying the duplicated OS
   // primitive.
diff --git a/base/memory/shared_memory_fuchsia.cc b/base/memory/shared_memory_fuchsia.cc
index a386913..878906a 100644
--- a/base/memory/shared_memory_fuchsia.cc
+++ b/base/memory/shared_memory_fuchsia.cc
@@ -38,13 +38,6 @@
   handle.Close();
 }
 
-// static
-size_t SharedMemory::GetHandleLimit() {
-  // Duplicated from the internal Magenta kernel constant kMaxHandleCount
-  // (kernel/lib/zircon/zircon.cpp).
-  return 256 * 1024u;
-}
-
 bool SharedMemory::CreateAndMapAnonymous(size_t size) {
   return CreateAnonymous(size) && Map(size);
 }
diff --git a/base/memory/shared_memory_mac.cc b/base/memory/shared_memory_mac.cc
index fc1af80..82dfeae 100644
--- a/base/memory/shared_memory_mac.cc
+++ b/base/memory/shared_memory_mac.cc
@@ -99,11 +99,6 @@
 }
 
 // static
-size_t SharedMemory::GetHandleLimit() {
-  return GetMaxFds();
-}
-
-// static
 SharedMemoryHandle SharedMemory::DuplicateHandle(
     const SharedMemoryHandle& handle) {
   return handle.Duplicate();
diff --git a/base/memory/shared_memory_posix.cc b/base/memory/shared_memory_posix.cc
index 0988b5a..f92a916 100644
--- a/base/memory/shared_memory_posix.cc
+++ b/base/memory/shared_memory_posix.cc
@@ -60,11 +60,6 @@
 }
 
 // static
-size_t SharedMemory::GetHandleLimit() {
-  return GetMaxFds();
-}
-
-// static
 SharedMemoryHandle SharedMemory::DuplicateHandle(
     const SharedMemoryHandle& handle) {
   return handle.Duplicate();
diff --git a/base/memory/shared_memory_win.cc b/base/memory/shared_memory_win.cc
index 88e3cbb..b0adabe 100644
--- a/base/memory/shared_memory_win.cc
+++ b/base/memory/shared_memory_win.cc
@@ -163,13 +163,6 @@
 }
 
 // static
-size_t SharedMemory::GetHandleLimit() {
-  // Rounded down from value reported here:
-  // http://blogs.technet.com/b/markrussinovich/archive/2009/09/29/3283844.aspx
-  return static_cast<size_t>(1 << 23);
-}
-
-// static
 SharedMemoryHandle SharedMemory::DuplicateHandle(
     const SharedMemoryHandle& handle) {
   return handle.Duplicate();
diff --git a/base/message_loop/message_pump_fuchsia.cc b/base/message_loop/message_pump_fuchsia.cc
index 25b255d..948785d 100644
--- a/base/message_loop/message_pump_fuchsia.cc
+++ b/base/message_loop/message_pump_fuchsia.cc
@@ -92,14 +92,6 @@
 
   controller->handler = nullptr;
 
-  // |signal| can include other spurious things, in particular, that an fd
-  // is writable, when we only asked to know when it was readable. In that
-  // case, we don't want to call both the CanWrite and CanRead callback,
-  // when the caller asked for only, for example, readable callbacks. So,
-  // mask with the events that we actually wanted to know about.
-  zx_signals_t signals = signal->trigger & signal->observed;
-  DCHECK_NE(0u, signals);
-
   // In the case of a persistent Watch, the Watch may be stopped and
   // potentially deleted by the caller within the callback, in which case
   // |controller| should not be accessed again, and we mustn't continue the
@@ -108,7 +100,7 @@
   bool was_stopped = false;
   controller->was_stopped_ = &was_stopped;
 
-  controller->watcher_->OnZxHandleSignalled(wait->object, signals);
+  controller->watcher_->OnZxHandleSignalled(wait->object, signal->observed);
 
   if (was_stopped)
     return;
@@ -125,6 +117,14 @@
   uint32_t events;
   fdio_unsafe_wait_end(io_, signals, &events);
 
+  // |events| can include other spurious things, in particular, that an fd
+  // is writable, when we only asked to know when it was readable. In that
+  // case, we don't want to call both the CanWrite and CanRead callback,
+  // when the caller asked for only, for example, readable callbacks. So,
+  // mask with the events that we actually wanted to know about.
+  events &= desired_events_;
+  DCHECK_NE(0u, events);
+
   // Each |watcher_| callback we invoke may stop or delete |this|. The pump has
   // set |was_stopped_| to point to a safe location on the calling stack, so we
   // can use that to detect being stopped mid-callback and avoid doing further
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc
index b2335a7..09eb38f 100644
--- a/base/metrics/field_trial.cc
+++ b/base/metrics/field_trial.cc
@@ -1217,6 +1217,8 @@
   win::ScopedHandle scoped_handle(handle);
 #elif defined(OS_MACOSX) && !defined(OS_IOS)
   auto* rendezvous = MachPortRendezvousClient::GetInstance();
+  if (!rendezvous)
+    return ReadOnlySharedMemoryRegion();
   mac::ScopedMachSendRight scoped_handle =
       rendezvous->TakeSendRight(field_trial_handle);
   if (!scoped_handle.is_valid())
diff --git a/base/process/process_metrics.h b/base/process/process_metrics.h
index f445125..2599109 100644
--- a/base/process/process_metrics.h
+++ b/base/process/process_metrics.h
@@ -254,6 +254,9 @@
 // at once. If the number is unavailable, a conservative best guess is returned.
 BASE_EXPORT size_t GetMaxFds();
 
+// Returns the maximum number of handles that can be open at once per process.
+BASE_EXPORT size_t GetHandleLimit();
+
 #if defined(OS_POSIX)
 // Increases the file descriptor soft limit to |max_descriptors| or the OS hard
 // limit, whichever is lower. If the limit is already higher than
diff --git a/base/process/process_metrics_fuchsia.cc b/base/process/process_metrics_fuchsia.cc
index ddfa9c6..3c7d14c 100644
--- a/base/process/process_metrics_fuchsia.cc
+++ b/base/process/process_metrics_fuchsia.cc
@@ -12,6 +12,12 @@
   return FDIO_MAX_FD;
 }
 
+size_t GetHandleLimit() {
+  // Duplicated from the internal Magenta kernel constant kMaxHandleCount
+  // (zircon/kernel/object/handle.cc).
+  return 256 * 1024u;
+}
+
 size_t GetSystemCommitCharge() {
   // TODO(https://crbug.com/926581): Fuchsia does not support this.
   return 0;
diff --git a/base/process/process_metrics_posix.cc b/base/process/process_metrics_posix.cc
index a09bbf2..5763432 100644
--- a/base/process/process_metrics_posix.cc
+++ b/base/process/process_metrics_posix.cc
@@ -71,6 +71,15 @@
   return static_cast<size_t>(max_fds);
 }
 
+size_t GetHandleLimit() {
+#if defined(OS_MACOSX)
+  // Taken from a small test that allocated ports in a loop.
+  return static_cast<size_t>(1 << 18);
+#else
+  return GetMaxFds();
+#endif
+}
+
 void IncreaseFdLimitTo(unsigned int max_descriptors) {
   struct rlimit limits;
   if (getrlimit(RLIMIT_NOFILE, &limits) == 0) {
diff --git a/base/process/process_metrics_win.cc b/base/process/process_metrics_win.cc
index fcf69ac..5cf0943 100644
--- a/base/process/process_metrics_win.cc
+++ b/base/process/process_metrics_win.cc
@@ -129,6 +129,12 @@
   return std::numeric_limits<size_t>::max();
 }
 
+size_t GetHandleLimit() {
+  // Rounded down from value reported here:
+  // http://blogs.technet.com/b/markrussinovich/archive/2009/09/29/3283844.aspx
+  return static_cast<size_t>(1 << 23);
+}
+
 // static
 std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics(
     ProcessHandle process) {
diff --git a/base/task/promise/dependent_list.cc b/base/task/promise/dependent_list.cc
index 81cc763..6cbbf34 100644
--- a/base/task/promise/dependent_list.cc
+++ b/base/task/promise/dependent_list.cc
@@ -47,7 +47,7 @@
 
 DependentList::Node::Node() = default;
 
-DependentList::Node::Node(Node&& other) {
+DependentList::Node::Node(Node&& other) noexcept {
   prerequisite_ = other.prerequisite_.load(std::memory_order_relaxed);
   other.prerequisite_ = 0;
   dependent_ = std::move(other.dependent_);
diff --git a/base/task/promise/finally_executor.cc b/base/task/promise/finally_executor.cc
index 27cc023..52da567 100644
--- a/base/task/promise/finally_executor.cc
+++ b/base/task/promise/finally_executor.cc
@@ -7,7 +7,8 @@
 namespace base {
 namespace internal {
 
-FinallyExecutorCommon::FinallyExecutorCommon(internal::CallbackBase&& callback)
+FinallyExecutorCommon::FinallyExecutorCommon(
+    internal::CallbackBase&& callback) noexcept
     : callback_(std::move(callback)) {}
 
 FinallyExecutorCommon::~FinallyExecutorCommon() = default;
diff --git a/base/task/promise/helpers.cc b/base/task/promise/helpers.cc
index 5008363..35dc37d 100644
--- a/base/task/promise/helpers.cc
+++ b/base/task/promise/helpers.cc
@@ -40,7 +40,7 @@
     const scoped_refptr<TaskRunner>& task_runner,
     const Location& from_here,
     AbstractPromise* prerequsite,
-    internal::PromiseExecutor::Data&& executor_data) {
+    internal::PromiseExecutor::Data&& executor_data) noexcept {
   return internal::AbstractPromise::Create(
       task_runner, from_here,
       std::make_unique<AbstractPromise::AdjacencyList>(prerequsite),
diff --git a/base/task/sequence_manager/task_queue.cc b/base/task/sequence_manager/task_queue.cc
index a69f9d30..35ff9d7 100644
--- a/base/task/sequence_manager/task_queue.cc
+++ b/base/task/sequence_manager/task_queue.cc
@@ -329,6 +329,10 @@
   impl_->SetObserver(observer);
 }
 
+void TaskQueue::SetShouldReportPostedTasksWhenDisabled(bool should_report) {
+  impl_->SetShouldReportPostedTasksWhenDisabled(should_report);
+}
+
 bool TaskQueue::IsOnMainThread() const {
   return associated_thread_->IsBoundToCurrentThread();
 }
diff --git a/base/task/sequence_manager/task_queue.h b/base/task/sequence_manager/task_queue.h
index f57b4a1..d863308 100644
--- a/base/task/sequence_manager/task_queue.h
+++ b/base/task/sequence_manager/task_queue.h
@@ -325,6 +325,12 @@
 
   void SetObserver(Observer* observer);
 
+  // Controls whether or not the queue will emit traces events when tasks are
+  // posted to it while disabled. This only applies for the current or next
+  // period during which the queue is disabled. When the queue is re-enabled
+  // this will revert back to the default value of false.
+  void SetShouldReportPostedTasksWhenDisabled(bool should_report);
+
   // Create a task runner for this TaskQueue which will annotate all
   // posted tasks with the given task type.
   // May be called on any thread.
diff --git a/base/task/sequence_manager/task_queue_impl.cc b/base/task/sequence_manager/task_queue_impl.cc
index 0ad98e8..a46b72d 100644
--- a/base/task/sequence_manager/task_queue_impl.cc
+++ b/base/task/sequence_manager/task_queue_impl.cc
@@ -136,6 +136,9 @@
 
 TaskQueueImpl::AnyThread::~AnyThread() = default;
 
+TaskQueueImpl::AnyThread::TracingOnly::TracingOnly() = default;
+TaskQueueImpl::AnyThread::TracingOnly::~TracingOnly() = default;
+
 TaskQueueImpl::MainThreadOnly::MainThreadOnly(TaskQueueImpl* task_queue,
                                               TimeDomain* time_domain)
     : time_domain(time_domain),
@@ -143,10 +146,7 @@
           new WorkQueue(task_queue, "delayed", WorkQueue::QueueType::kDelayed)),
       immediate_work_queue(new WorkQueue(task_queue,
                                          "immediate",
-                                         WorkQueue::QueueType::kImmediate)),
-      is_enabled(true),
-      blame_context(nullptr),
-      is_enabled_for_test(true) {}
+                                         WorkQueue::QueueType::kImmediate)) {}
 
 TaskQueueImpl::MainThreadOnly::~MainThreadOnly() = default;
 
@@ -290,6 +290,8 @@
 
     sequence_manager_->WillQueueTask(
         &any_thread_.immediate_incoming_queue.back(), name_);
+    MaybeReportIpcTaskQueuedFromAnyThreadLocked(
+        &any_thread_.immediate_incoming_queue.back(), name_);
 
     // If this queue was completely empty, then the SequenceManager needs to be
     // informed so it can reload the work queue and add us to the
@@ -386,8 +388,10 @@
   pending_task.cross_thread_ = false;
 #endif
 
-  if (notify_task_annotator)
+  if (notify_task_annotator) {
     sequence_manager_->WillQueueTask(&pending_task, name_);
+    MaybeReportIpcTaskQueuedFromMainThread(&pending_task, name_);
+  }
   main_thread_only().delayed_incoming_queue.push(std::move(pending_task));
 
   LazyNow lazy_now(now);
@@ -398,6 +402,7 @@
 
 void TaskQueueImpl::PushOntoDelayedIncomingQueue(Task pending_task) {
   sequence_manager_->WillQueueTask(&pending_task, name_);
+  MaybeReportIpcTaskQueuedFromAnyThreadUnlocked(&pending_task, name_);
 
 #if DCHECK_IS_ON()
   pending_task.cross_thread_ = true;
@@ -880,29 +885,45 @@
 }
 
 void TaskQueueImpl::SetQueueEnabled(bool enabled) {
-  if (main_thread_only().is_enabled != enabled) {
-    main_thread_only().is_enabled = enabled;
-    EnableOrDisableWithSelector(enabled);
-  }
-}
-
-void TaskQueueImpl::EnableOrDisableWithSelector(bool enable) {
-  // |sequence_manager_| can be null in tests.
-  if (!sequence_manager_)
+  if (main_thread_only().is_enabled == enabled)
     return;
 
+  // Update the |main_thread_only_| struct.
+  main_thread_only().is_enabled = enabled;
+  main_thread_only().disabled_time = nullopt;
+  if (!enabled) {
+    bool tracing_enabled = false;
+    TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                                       &tracing_enabled);
+    main_thread_only().disabled_time = main_thread_only().time_domain->Now();
+  } else {
+    // Override reporting if the queue is becoming enabled again.
+    main_thread_only().should_report_posted_tasks_when_disabled = false;
+  }
+
   LazyNow lazy_now = main_thread_only().time_domain->CreateLazyNow();
   UpdateDelayedWakeUp(&lazy_now);
 
-  bool has_pending_immediate_work;
+  bool has_pending_immediate_work = false;
 
   {
     base::internal::CheckedAutoLock lock(any_thread_lock_);
     UpdateCrossThreadQueueStateLocked();
     has_pending_immediate_work = HasPendingImmediateWorkLocked();
+
+    // Copy over the task-reporting related state.
+    any_thread_.tracing_only.is_enabled = enabled;
+    any_thread_.tracing_only.disabled_time = main_thread_only().disabled_time;
+    any_thread_.tracing_only.should_report_posted_tasks_when_disabled =
+        main_thread_only().should_report_posted_tasks_when_disabled;
   }
 
-  if (enable) {
+  // |sequence_manager_| can be null in tests.
+  if (!sequence_manager_)
+    return;
+
+  // Finally, enable or disable the queue with the selector.
+  if (enabled) {
     if (has_pending_immediate_work && main_thread_only().task_queue_observer) {
       // Delayed work notification will be issued via time domain.
       main_thread_only().task_queue_observer->OnQueueNextWakeUpChanged(
@@ -917,6 +938,30 @@
   }
 }
 
+void TaskQueueImpl::SetShouldReportPostedTasksWhenDisabled(bool should_report) {
+  if (main_thread_only().should_report_posted_tasks_when_disabled ==
+      should_report)
+    return;
+
+  // Only observe transitions turning the reporting on if tracing is enabled.
+  if (should_report) {
+    bool tracing_enabled = false;
+    TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                                       &tracing_enabled);
+    if (!tracing_enabled)
+      return;
+  }
+
+  main_thread_only().should_report_posted_tasks_when_disabled = should_report;
+
+  // Mirror the state to the AnyThread struct as well.
+  {
+    base::internal::CheckedAutoLock lock(any_thread_lock_);
+    any_thread_.tracing_only.should_report_posted_tasks_when_disabled =
+        should_report;
+  }
+}
+
 void TaskQueueImpl::UpdateCrossThreadQueueStateLocked() {
   any_thread_.immediate_work_queue_empty =
       main_thread_only().immediate_work_queue->Empty();
@@ -1139,6 +1184,107 @@
   return false;
 }
 
+void TaskQueueImpl::MaybeReportIpcTaskQueuedFromMainThread(
+    Task* pending_task,
+    const char* task_queue_name) {
+  if (!pending_task->ipc_hash)
+    return;
+
+  // It's possible that tracing was just enabled and no disabled time has been
+  // stored. In that case, skip emitting the event.
+  if (!main_thread_only().disabled_time)
+    return;
+
+  bool tracing_enabled = false;
+  TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                                     &tracing_enabled);
+  if (!tracing_enabled)
+    return;
+
+  if (main_thread_only().is_enabled ||
+      !main_thread_only().should_report_posted_tasks_when_disabled) {
+    return;
+  }
+
+  base::TimeDelta time_since_disabled =
+      main_thread_only().time_domain->Now() -
+      main_thread_only().disabled_time.value();
+
+  ReportIpcTaskQueued(pending_task, task_queue_name, time_since_disabled);
+}
+
+bool TaskQueueImpl::ShouldReportIpcTaskQueuedFromAnyThreadLocked(
+    base::TimeDelta* time_since_disabled) {
+  // It's possible that tracing was just enabled and no disabled time has been
+  // stored. In that case, skip emitting the event.
+  if (!any_thread_.tracing_only.disabled_time)
+    return false;
+
+  if (any_thread_.tracing_only.is_enabled ||
+      any_thread_.tracing_only.should_report_posted_tasks_when_disabled) {
+    return false;
+  }
+
+  *time_since_disabled = any_thread_.time_domain->Now() -
+                         any_thread_.tracing_only.disabled_time.value();
+  return true;
+}
+
+void TaskQueueImpl::MaybeReportIpcTaskQueuedFromAnyThreadLocked(
+    Task* pending_task,
+    const char* task_queue_name) {
+  if (!pending_task->ipc_hash)
+    return;
+
+  bool tracing_enabled = false;
+  TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                                     &tracing_enabled);
+  if (!tracing_enabled)
+    return;
+
+  base::TimeDelta time_since_disabled;
+  if (ShouldReportIpcTaskQueuedFromAnyThreadLocked(&time_since_disabled))
+    ReportIpcTaskQueued(pending_task, task_queue_name, time_since_disabled);
+}
+
+void TaskQueueImpl::MaybeReportIpcTaskQueuedFromAnyThreadUnlocked(
+    Task* pending_task,
+    const char* task_queue_name) {
+  if (!pending_task->ipc_hash)
+    return;
+
+  bool tracing_enabled = false;
+  TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                                     &tracing_enabled);
+  if (!tracing_enabled)
+    return;
+
+  base::TimeDelta time_since_disabled;
+  bool should_report = false;
+  {
+    base::internal::CheckedAutoLock lock(any_thread_lock_);
+    should_report =
+        ShouldReportIpcTaskQueuedFromAnyThreadLocked(&time_since_disabled);
+  }
+
+  ReportIpcTaskQueued(pending_task, task_queue_name, time_since_disabled);
+}
+
+void TaskQueueImpl::ReportIpcTaskQueued(
+    Task* pending_task,
+    const char* task_queue_name,
+    const base::TimeDelta& time_since_disabled) {
+  // Use a begin/end event pair so we can get 4 fields in the event.
+  TRACE_EVENT_BEGIN2(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                     "task_posted_to_disabled_queue", "task_queue_name",
+                     task_queue_name, "time_since_disabled_ms",
+                     time_since_disabled.InMilliseconds());
+  TRACE_EVENT_END2(TRACE_DISABLED_BY_DEFAULT("lifecycles"),
+                   "task_posted_to_disabled_queue", "ipc_hash",
+                   pending_task->ipc_hash, "location",
+                   pending_task->posted_from.program_counter());
+}
+
 TaskQueueImpl::DelayedIncomingQueue::DelayedIncomingQueue() = default;
 TaskQueueImpl::DelayedIncomingQueue::~DelayedIncomingQueue() = default;
 
diff --git a/base/task/sequence_manager/task_queue_impl.h b/base/task/sequence_manager/task_queue_impl.h
index d5a4ea6..ed5489d 100644
--- a/base/task/sequence_manager/task_queue_impl.h
+++ b/base/task/sequence_manager/task_queue_impl.h
@@ -102,6 +102,7 @@
   const char* GetName() const;
   bool IsQueueEnabled() const;
   void SetQueueEnabled(bool enabled);
+  void SetShouldReportPostedTasksWhenDisabled(bool should_report);
   bool IsEmpty() const;
   size_t GetNumberOfPendingTasks() const;
   bool HasTaskToRunImmediately() const;
@@ -340,8 +341,8 @@
     DelayedIncomingQueue delayed_incoming_queue;
     ObserverList<TaskObserver>::Unchecked task_observers;
     base::internal::HeapHandle heap_handle;
-    bool is_enabled;
-    trace_event::BlameContext* blame_context;  // Not owned.
+    bool is_enabled = true;
+    trace_event::BlameContext* blame_context = nullptr;  // Not owned.
     EnqueueOrder current_fence;
     Optional<TimeTicks> delayed_fence;
     OnTaskStartedHandler on_task_started_handler;
@@ -350,7 +351,13 @@
     // excessive calls.
     Optional<DelayedWakeUp> scheduled_wake_up;
     // If false, queue will be disabled. Used only for tests.
-    bool is_enabled_for_test;
+    bool is_enabled_for_test = true;
+    // The time at which the task queue was disabled, if it is currently
+    // disabled.
+    Optional<TimeTicks> disabled_time;
+    // Whether or not the task queue should emit tracing events for tasks
+    // posted to this queue when it is disabled.
+    bool should_report_posted_tasks_when_disabled = false;
   };
 
   void PostTask(PostedTask task);
@@ -394,8 +401,6 @@
                               TimeTicks now,
                               trace_event::TracedValue* state);
 
-  void EnableOrDisableWithSelector(bool enable);
-
   // Schedules delayed work on time domain and calls the observer.
   void UpdateDelayedWakeUp(LazyNow* lazy_now);
   void UpdateDelayedWakeUpImpl(LazyNow* lazy_now,
@@ -411,6 +416,23 @@
   void MaybeLogPostTask(PostedTask* task);
   void MaybeAdjustTaskDelay(PostedTask* task, CurrentThread current_thread);
 
+  // Reports the task if it was due to IPC and was posted to a disabled queue.
+  // This should be called after WillQueueTask has been called for the task.
+  void MaybeReportIpcTaskQueuedFromMainThread(Task* pending_task,
+                                              const char* task_queue_name);
+  bool ShouldReportIpcTaskQueuedFromAnyThreadLocked(
+      base::TimeDelta* time_since_disabled)
+      EXCLUSIVE_LOCKS_REQUIRED(any_thread_lock_);
+  void MaybeReportIpcTaskQueuedFromAnyThreadLocked(Task* pending_task,
+                                                   const char* task_queue_name)
+      EXCLUSIVE_LOCKS_REQUIRED(any_thread_lock_);
+  void MaybeReportIpcTaskQueuedFromAnyThreadUnlocked(
+      Task* pending_task,
+      const char* task_queue_name);
+  void ReportIpcTaskQueued(Task* pending_task,
+                           const char* task_queue_name,
+                           const base::TimeDelta& time_since_disabled);
+
   const char* name_;
   SequenceManagerImpl* const sequence_manager_;
 
@@ -421,6 +443,16 @@
   mutable base::internal::CheckedLock any_thread_lock_;
 
   struct AnyThread {
+    // Mirrored from MainThreadOnly. These are only used for tracing.
+    struct TracingOnly {
+      TracingOnly();
+      ~TracingOnly();
+
+      bool is_enabled = true;
+      Optional<TimeTicks> disabled_time;
+      bool should_report_posted_tasks_when_disabled = false;
+    };
+
     explicit AnyThread(TimeDomain* time_domain);
     ~AnyThread();
 
@@ -441,12 +473,14 @@
     bool unregistered = false;
 
 #if DCHECK_IS_ON()
-    // A cached of |immediate_work_queue->work_queue_set_index()| which is used
+    // A cache of |immediate_work_queue->work_queue_set_index()| which is used
     // to index into
     // SequenceManager::Settings::per_priority_cross_thread_task_delay to apply
     // a priority specific delay for debugging purposes.
     int queue_set_index = 0;
 #endif
+
+    TracingOnly tracing_only;
   };
 
   AnyThread any_thread_ GUARDED_BY(any_thread_lock_);
diff --git a/base/task/thread_pool/task_source.cc b/base/task/thread_pool/task_source.cc
index de0fcd3..b978251 100644
--- a/base/task/thread_pool/task_source.cc
+++ b/base/task/thread_pool/task_source.cc
@@ -15,7 +15,7 @@
 namespace base {
 namespace internal {
 
-TaskSource::RunIntent::RunIntent(RunIntent&& other)
+TaskSource::RunIntent::RunIntent(RunIntent&& other) noexcept
     : task_source_(other.task_source_),
       concurrency_status_(other.concurrency_status_) {
   other.task_source_ = nullptr;
@@ -111,8 +111,8 @@
 RegisteredTaskSource::RegisteredTaskSource(std::nullptr_t)
     : RegisteredTaskSource() {}
 
-RegisteredTaskSource::RegisteredTaskSource(RegisteredTaskSource&& other) =
-    default;
+RegisteredTaskSource::RegisteredTaskSource(
+    RegisteredTaskSource&& other) noexcept = default;
 
 RegisteredTaskSource::~RegisteredTaskSource() {
   Unregister();
diff --git a/base/trace_event/builtin_categories.h b/base/trace_event/builtin_categories.h
index 2225bfc..426860c 100644
--- a/base/trace_event/builtin_categories.h
+++ b/base/trace_event/builtin_categories.h
@@ -177,6 +177,7 @@
   X(TRACE_DISABLED_BY_DEFAULT("gpu.service"))                            \
   X(TRACE_DISABLED_BY_DEFAULT("ipc.flow"))                               \
   X(TRACE_DISABLED_BY_DEFAULT("layer-element"))                          \
+  X(TRACE_DISABLED_BY_DEFAULT("lifecycles"))                             \
   X(TRACE_DISABLED_BY_DEFAULT("loading"))                                \
   X(TRACE_DISABLED_BY_DEFAULT("memory-infra"))                           \
   X(TRACE_DISABLED_BY_DEFAULT("memory-infra.v8.code_stats"))             \
diff --git a/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java b/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java
index efefcbc..56b84845 100644
--- a/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java
+++ b/build/android/bytecode/java/org/chromium/bytecode/ByteCodeProcessor.java
@@ -113,15 +113,20 @@
             writer = new ClassWriter(reader, 0);
         }
         ClassVisitor chain = writer;
-        /* DEBUGGING:
-         To see the bytecode for a specific class:
-           if (entry.getName().contains("YourClassName")) {
-             chain = new TraceClassVisitor(chain, new PrintWriter(System.out));
-           }
+        /* DEBUGGING:}
          To see objectweb.asm code that will generate bytecode for a given class:
-           java -cp "third_party/ow2_asm/lib/asm-5.0.1.jar:third_party/ow2_asm/lib/"\
-               "asm-util-5.0.1.jar:out/Debug/lib.java/jar_containing_yourclass.jar" \
-               org.objectweb.asm.util.ASMifier org.package.YourClassName
+
+         java -cp
+         "third_party/ow2_asm/lib/asm.jar:third_party/ow2_asm/lib/asm-util.jar:out/Debug/lib.java/jar_containing_yourclass.jar"
+         org.objectweb.asm.util.ASMifier org.package.YourClassName
+
+         See this pdf for more details: https://asm.ow2.io/asm4-guide.pdf
+
+         To see the bytecode for a specific class, uncomment this code with your class name:
+
+        if (entry.getName().contains("YOUR_CLASS_NAME")) {
+          chain = new TraceClassVisitor(chain, new PrintWriter(System.out));
+        }
         */
         if (sShouldUseThreadAnnotations) {
             chain = new ThreadAssertionClassAdapter(chain);
diff --git a/build/android/bytecode/java/org/chromium/bytecode/SplitCompatClassAdapter.java b/build/android/bytecode/java/org/chromium/bytecode/SplitCompatClassAdapter.java
index 8d6ae69..d8aa284 100644
--- a/build/android/bytecode/java/org/chromium/bytecode/SplitCompatClassAdapter.java
+++ b/build/android/bytecode/java/org/chromium/bytecode/SplitCompatClassAdapter.java
@@ -6,6 +6,7 @@
 
 import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
 import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 import static org.objectweb.asm.Opcodes.INVOKESTATIC;
 import static org.objectweb.asm.Opcodes.RETURN;
@@ -34,6 +35,9 @@
 
     private static final String MODULE_INSTALLER_CLASS_NAME =
             "org/chromium/components/module_installer/ModuleInstaller";
+    private static final String GET_INSTANCE_METHOD_NAME = "getInstance";
+    private static final String GET_INSTANCE_DESCRIPTOR =
+            TypeUtils.getMethodDescriptor(MODULE_INSTALLER_CLASS_NAME);
     private static final String INIT_ACTIVITY_METHOD_NAME = "initActivity";
     private static final String INIT_ACTIVITY_DESCRIPTOR =
             TypeUtils.getMethodDescriptor(VOID, CONTEXT);
@@ -107,7 +111,7 @@
      * <pre>
      * protected void attachBaseContext(Context base) {
      *     super.attachBaseContext(base);
-     *     ModuleInstaller.initActivity(this);
+     *     ModuleInstaller.getInstance().initActivity(this);
      * }
      * </pre>
      */
@@ -115,16 +119,30 @@
         MethodVisitor mv = super.visitMethod(ACC_PROTECTED, ATTACH_BASE_CONTEXT_METHOD_NAME,
                 ATTACH_BASE_CONTEXT_DESCRIPTOR, null, null);
         mv.visitCode();
-        mv.visitVarInsn(ALOAD, 0); // load "this" on stack
-        mv.visitVarInsn(ALOAD, 1); // load first method parameter on stack (Context)
+        // Push "this" on stack.
+        mv.visitVarInsn(ALOAD, 0);
+        // Push first method parameter on stack (Context).
+        mv.visitVarInsn(ALOAD, 1);
+        // Pop argument from stack (Context).
+        // Pop target object from stack ("this").
+        // Calls attachBaseContext.
         mv.visitMethodInsn(INVOKESPECIAL, ANDROID_APP_ACTIVITY_CLASS_NAME,
-                ATTACH_BASE_CONTEXT_METHOD_NAME,
-                ATTACH_BASE_CONTEXT_DESCRIPTOR); // invoke super's attach base context
-        mv.visitVarInsn(ALOAD, 0); // load "this" on stack
-        mv.visitMethodInsn(INVOKESTATIC, MODULE_INSTALLER_CLASS_NAME, INIT_ACTIVITY_METHOD_NAME,
-                INIT_ACTIVITY_DESCRIPTOR);
+                ATTACH_BASE_CONTEXT_METHOD_NAME, ATTACH_BASE_CONTEXT_DESCRIPTOR, false);
+        // Push return value on stack (ModuleInstaller).
+        // Calls getInstance.
+        mv.visitMethodInsn(INVOKESTATIC, MODULE_INSTALLER_CLASS_NAME, GET_INSTANCE_METHOD_NAME,
+                GET_INSTANCE_DESCRIPTOR, false);
+        // Push "this" on stack.
+        mv.visitVarInsn(ALOAD, 0);
+        // Pop argument from stack ("this").
+        // Pop target object from stack (ModuleInstaller).
+        // Calls initActivity.
+        mv.visitMethodInsn(INVOKEINTERFACE, MODULE_INSTALLER_CLASS_NAME, INIT_ACTIVITY_METHOD_NAME,
+                INIT_ACTIVITY_DESCRIPTOR, true);
         mv.visitInsn(RETURN);
-        mv.visitMaxs(2, 2); // max stack size - 2, max locals - 2
+        // Max stack size = 2 (Only push at most 2 before popping).
+        // Max locals = 2 ("this" and 1 parameter).
+        mv.visitMaxs(2, 2);
         mv.visitEnd();
     }
 
diff --git a/build/android/gyp/compile_resources.py b/build/android/gyp/compile_resources.py
index 57efc8b..199d8bcb 100755
--- a/build/android/gyp/compile_resources.py
+++ b/build/android/gyp/compile_resources.py
@@ -139,6 +139,13 @@
       help='Use resource IDs generated by aapt --emit-ids.')
 
   input_opts.add_argument(
+      '--extra-main-r-text-files',
+      help='Additional R.txt files that will be added to the root R.java file, '
+      'but not packaged in the generated resources.arsc. If these resources '
+      'entries contain duplicate resources with the generated R.txt file, they '
+      'must be identical.')
+
+  input_opts.add_argument(
       '--support-zh-hk',
       action='store_true',
       help='Use zh-rTW resources for zh-rHK.')
@@ -157,7 +164,8 @@
       required=True,
       help="android:targetSdkVersion for APK.")
   input_opts.add_argument(
-      '--max-sdk-version', help="android:maxSdkVersion for APK.")
+      '--max-sdk-version',
+      help="android:maxSdkVersion expected in AndroidManifest.xml.")
   input_opts.add_argument(
       '--manifest-package', help='Package name of the AndroidManifest.xml.')
 
@@ -244,6 +252,8 @@
       options.shared_resources_whitelist_locales)
   options.resource_blacklist_exceptions = build_utils.ParseGnList(
       options.resource_blacklist_exceptions)
+  options.extra_main_r_text_files = build_utils.ParseGnList(
+      options.extra_main_r_text_files)
 
   if options.optimized_proto_path and not options.proto_path:
     # We could write to a temp file, but it's simpler to require it.
@@ -467,8 +477,13 @@
       options.android_manifest)
 
   manifest_utils.AssertUsesSdk(manifest_node, options.min_sdk_version,
-                               options.target_sdk_version,
-                               options.max_sdk_version)
+                               options.target_sdk_version)
+  # We explicitly check that maxSdkVersion is set in the manifest since we don't
+  # add it later like minSdkVersion and targetSdkVersion.
+  manifest_utils.AssertUsesSdk(
+      manifest_node,
+      max_sdk_version=options.max_sdk_version,
+      fail_if_not_exist=True)
   manifest_utils.AssertPackage(manifest_node, options.manifest_package)
 
   manifest_node.set('platformBuildVersionCode', version_code)
@@ -966,7 +981,8 @@
     resource_utils.CreateRJavaFiles(
         build.srcjar_dir, None, build.r_txt_path, options.extra_res_packages,
         options.extra_r_text_files, rjava_build_options, options.srcjar_out,
-        custom_root_package_name, grandparent_custom_package_name)
+        custom_root_package_name, grandparent_custom_package_name,
+        options.extra_main_r_text_files)
 
     build_utils.ZipDir(build.srcjar_path, build.srcjar_dir)
 
diff --git a/build/android/gyp/merge_manifest.py b/build/android/gyp/merge_manifest.py
index a7b2eff..5680ad9 100755
--- a/build/android/gyp/merge_manifest.py
+++ b/build/android/gyp/merge_manifest.py
@@ -30,14 +30,15 @@
 
 @contextlib.contextmanager
 def _ProcessManifest(manifest_path, min_sdk_version, target_sdk_version,
-                     manifest_package):
+                     max_sdk_version, manifest_package):
   """Patches an Android manifest to always include the 'tools' namespace
   declaration, as it is not propagated by the manifest merger from the SDK.
 
   See https://issuetracker.google.com/issues/63411481
   """
   doc, manifest, _ = manifest_utils.ParseManifest(manifest_path)
-  manifest_utils.AssertUsesSdk(manifest, min_sdk_version, target_sdk_version)
+  manifest_utils.AssertUsesSdk(manifest, min_sdk_version, target_sdk_version,
+                               max_sdk_version)
   assert manifest_utils.GetPackage(manifest) or manifest_package, \
             'Must set manifest package in GN or in AndroidManifest.xml'
   manifest_utils.AssertPackage(manifest, manifest_package)
@@ -80,6 +81,8 @@
       required=True,
       help='android:targetSdkVersion for merging.')
   parser.add_argument(
+      '--max-sdk-version', help='android:maxSdkVersion for merging.')
+  parser.add_argument(
       '--manifest-package',
       help='Package name of the merged AndroidManifest.xml.')
   args = parser.parse_args(argv)
@@ -95,14 +98,24 @@
         _MANIFEST_MERGER_MAIN_CLASS,
         '--out',
         output.name,
+        '--property',
+        'MIN_SDK_VERSION=' + args.min_sdk_version,
+        '--property',
+        'TARGET_SDK_VERSION=' + args.target_sdk_version,
     ]
 
+    if args.max_sdk_version:
+      cmd += [
+          '--property',
+          'MAX_SDK_VERSION=' + args.max_sdk_version,
+      ]
+
     extras = build_utils.ParseGnList(args.extras)
     if extras:
       cmd += ['--libs', ':'.join(extras)]
 
     with _ProcessManifest(args.root_manifest, args.min_sdk_version,
-                          args.target_sdk_version,
+                          args.target_sdk_version, args.max_sdk_version,
                           args.manifest_package) as tup:
       root_manifest, package = tup
       cmd += [
@@ -110,10 +123,6 @@
           root_manifest,
           '--property',
           'PACKAGE=' + package,
-          '--property',
-          'MIN_SDK_VERSION=' + args.min_sdk_version,
-          '--property',
-          'TARGET_SDK_VERSION=' + args.target_sdk_version,
       ]
       build_utils.CheckOutput(cmd,
         # https://issuetracker.google.com/issues/63514300:
diff --git a/build/android/gyp/util/manifest_utils.py b/build/android/gyp/util/manifest_utils.py
index e036014..309230f 100644
--- a/build/android/gyp/util/manifest_utils.py
+++ b/build/android/gyp/util/manifest_utils.py
@@ -66,13 +66,15 @@
 
 
 def AssertUsesSdk(manifest_node,
-                  min_sdk_version,
+                  min_sdk_version=None,
                   target_sdk_version=None,
-                  max_sdk_version=None):
+                  max_sdk_version=None,
+                  fail_if_not_exist=False):
   """Asserts values of attributes of <uses-sdk> element.
 
-  Will only assert if both the passed value is not None and the value of
-  attribute exist.
+  Unless |fail_if_not_exist| is true, will only assert if both the passed value
+  is not None and the value of attribute exist. If |fail_if_not_exist| is true
+  will fail if passed value is not None but attribute does not exist.
   """
   uses_sdk_node = manifest_node.find('./uses-sdk')
   if uses_sdk_node is None:
@@ -81,6 +83,10 @@
                                                          target_sdk_version),
                               ('max', max_sdk_version)):
     value = uses_sdk_node.get('{%s}%sSdkVersion' % (ANDROID_NAMESPACE, prefix))
+    if fail_if_not_exist and not value and sdk_version:
+      assert False, (
+          '%sSdkVersion in Android manifest does not exist but we expect %s' %
+          (prefix, sdk_version))
     if not value or not sdk_version:
       continue
     assert value == sdk_version, (
diff --git a/build/android/gyp/util/resource_utils.py b/build/android/gyp/util/resource_utils.py
index 633d600..65061f5 100644
--- a/build/android/gyp/util/resource_utils.py
+++ b/build/android/gyp/util/resource_utils.py
@@ -335,7 +335,8 @@
                      rjava_build_options,
                      srcjar_out,
                      custom_root_package_name=None,
-                     grandparent_custom_package_name=None):
+                     grandparent_custom_package_name=None,
+                     extra_main_r_text_files=None):
   """Create all R.java files for a set of packages and R.txt files.
 
   Args:
@@ -358,6 +359,7 @@
       as the grandparent_custom_package_name. The format of this package name
       is identical to custom_root_package_name.
       (eg. for vr grandparent_custom_package_name would be "base")
+    extra_main_r_text_files: R.txt files to be added to the root R.java file.
   Raises:
     Exception if a package name appears several times in |extra_res_packages|
   """
@@ -378,12 +380,23 @@
   all_resources = {}
   all_resources_by_type = collections.defaultdict(list)
 
-  for entry in _ParseTextSymbolsFile(main_r_txt_file, fix_package_ids=True):
-    all_resources[(entry.resource_type, entry.name)] = entry
-    all_resources_by_type[entry.resource_type].append(entry)
-    assert entry.resource_type in _ALL_RESOURCE_TYPES, (
-        'Unknown resource type: %s, add to _ALL_RESOURCE_TYPES!' %
-        entry.resource_type)
+  main_r_text_files = [main_r_txt_file]
+  if extra_main_r_text_files:
+    main_r_text_files.extend(extra_main_r_text_files)
+  for r_txt_file in main_r_text_files:
+    for entry in _ParseTextSymbolsFile(r_txt_file, fix_package_ids=True):
+      entry_key = (entry.resource_type, entry.name)
+      if entry_key in all_resources:
+        assert entry == all_resources[entry_key], (
+            'Input R.txt %s provided a duplicate resource with a different '
+            'entry value. Got %s, expected %s.' % (r_txt_file, entry,
+                                                   all_resources[entry_key]))
+      else:
+        all_resources[entry_key] = entry
+        all_resources_by_type[entry.resource_type].append(entry)
+        assert entry.resource_type in _ALL_RESOURCE_TYPES, (
+            'Unknown resource type: %s, add to _ALL_RESOURCE_TYPES!' %
+            entry.resource_type)
 
   if custom_root_package_name:
     # Custom package name is available, thus use it for root_r_java_package.
diff --git a/build/android/gyp/write_build_config.py b/build/android/gyp/write_build_config.py
index b4d8c71..40de1fe 100755
--- a/build/android/gyp/write_build_config.py
+++ b/build/android/gyp/write_build_config.py
@@ -120,22 +120,17 @@
 [`android_app_bundle_module`](#target_android_app_bundle_module)
 type. Whether or not this module is the base module for some bundle.
 
-# Top-level `resources` dictionary:
-
-This dictionary only appears for a few target types that can contain or
-relate to Android resources (e.g. `android_resources` or `android_apk`):
-
-* `resources['dependency_zips']`:
+* `deps_info['dependency_zips']`:
 List of `deps_info['resources_zip']` entries for all `android_resources`
 dependencies for the current target.
 
-* `resource['extra_package_names']`:
+* `deps_info['extra_package_names']`:
 Always empty for `android_resources` types. Otherwise,
 the list of `deps_info['package_name']` entries for all `android_resources`
 dependencies for the current target. Computed automatically by
 `write_build_config.py`.
 
-* `resources['extra_r_text_files']`:
+* `deps_info['extra_r_text_files']`:
 Always empty for `android_resources` types. Otherwise, the list of
 `deps_info['r_text']` entries for all `android_resources` dependencies for
 the current target. Computed automatically.
@@ -840,7 +835,6 @@
   # android_resources options
   parser.add_option('--srcjar', help='Path to target\'s resources srcjar.')
   parser.add_option('--resources-zip', help='Path to target\'s resources zip.')
-  parser.add_option('--r-text', help='Path to target\'s R.txt file.')
   parser.add_option('--package-name',
       help='Java package name for these resources.')
   parser.add_option('--android-manifest', help='Path to android manifest.')
@@ -929,6 +923,19 @@
   parser.add_option('--incremental-install-json-path',
                     help="Path to the target's generated incremental install "
                     "json.")
+  parser.add_option(
+      '--tested-apk-config',
+      help='Path to the build config of the tested apk (for an instrumentation '
+      'test apk).')
+  parser.add_option(
+      '--proguard-enabled',
+      action='store_true',
+      help='Whether proguard is enabled for this apk or bundle module.')
+  parser.add_option(
+      '--proguard-configs',
+      help='GN-list of proguard flag files to use in final apk.')
+  parser.add_option(
+      '--proguard-mapping-path', help='Path to jar created by ProGuard step')
 
   # apk options that are static library specific
   parser.add_option(
@@ -936,22 +943,13 @@
       help='GN list of .build_configs of targets that use this target as a '
       'static library.')
   parser.add_option(
-      '--static-library-jar-path',
-      help='Equivalent to what normally would be the jar_path for the APK.')
-  parser.add_option(
       '--resource-ids-provider',
       help='Path to the .build_config for the APK that this static library '
       'target uses to generate stable resource IDs.')
 
-  parser.add_option('--tested-apk-config',
-      help='Path to the build config of the tested apk (for an instrumentation '
-      'test apk).')
-  parser.add_option('--proguard-enabled', action='store_true',
-      help='Whether proguard is enabled for this apk or bundle module.')
-  parser.add_option('--proguard-configs',
-      help='GN-list of proguard flag files to use in final apk.')
-  parser.add_option('--proguard-mapping-path',
-      help='Path to jar created by ProGuard step')
+  # options shared between android_resources and apk targets
+  parser.add_option('--r-text-path', help='Path to target\'s R.txt file.')
+
   parser.add_option('--fail',
       help='GN-list of error message lines to fail with.')
 
@@ -966,9 +964,6 @@
       '--module-pathmap-path',
       help='Path to pathmap file for resource paths in a bundle module.')
   parser.add_option(
-      '--module-rtxt-path',
-      help='Path to R.txt file for resources in a bundle module.')
-  parser.add_option(
       '--base-whitelist-rtxt-path',
       help='Path to R.txt file for the base resources whitelist.')
   parser.add_option(
@@ -1033,9 +1028,6 @@
     if options.apk_proto_resources:
       raise Exception('--apk-proto-resources can only be used with '
                       '--type=android_app_bundle_module')
-    if options.module_rtxt_path:
-      raise Exception('--module-rxt-path can only be used with '
-                      '--type=android_app_bundle_module')
     if options.module_pathmap_path:
       raise Exception('--module-pathmap-path can only be used with '
                       '--type=android_app_bundle_module')
@@ -1077,9 +1069,6 @@
     if options.type != 'android_apk':
       raise Exception(
           '--static-library-dependent-configs only supports --type=android_apk')
-    if not options.static_library_jar_path:
-      raise Exception('Can\'t have --static-library-dependent-configs without '
-                      '--static-library-jar-path')
   options.static_library_dependent_configs = build_utils.ParseGnList(
       options.static_library_dependent_configs)
   static_library_dependent_configs_by_path = {
@@ -1163,6 +1152,9 @@
       else:
         gradle['dependent_java_projects'].append(c['path'])
 
+  if options.r_text_path:
+    deps_info['r_text_path'] = options.r_text_path
+
   # TODO(tiborg): Remove creation of JNI info for type group and java_library
   # once we can generate the JNI registration based on APK / module targets as
   # opposed to groups and libraries.
@@ -1177,9 +1169,6 @@
     if options.apk_proto_resources:
       deps_info['proto_resources_path'] = options.apk_proto_resources
 
-    if options.module_rtxt_path:
-      deps_info['module_rtxt_path'] = options.module_rtxt_path
-
     if options.module_pathmap_path:
       deps_info['module_pathmap_path'] = options.module_pathmap_path
     else:
@@ -1256,8 +1245,6 @@
       deps_info['package_name'] = manifest.GetPackageName()
     if options.package_name:
       deps_info['package_name'] = options.package_name
-    if options.r_text:
-      deps_info['r_text'] = options.r_text
 
     deps_info['resources_dirs'] = []
     if options.resource_dirs:
@@ -1304,7 +1291,6 @@
   if options.type in (
       'android_resources', 'android_apk', 'junit_binary', 'resource_rewriter',
       'dist_aar', 'android_app_bundle_module'):
-    config['resources'] = {}
 
     dependency_zips = [
         c['resources_zip'] for c in all_resources_deps if c['resources_zip']
@@ -1316,32 +1302,33 @@
       extra_package_names = [
           c['package_name'] for c in all_resources_deps if 'package_name' in c]
       extra_r_text_files = [
-          c['r_text'] for c in all_resources_deps if 'r_text' in c]
+          c['r_text_path'] for c in all_resources_deps if 'r_text_path' in c
+      ]
 
     # For feature modules, remove any resources that already exist in the base
     # module.
     if base_module_build_config:
       dependency_zips = [
           c for c in dependency_zips
-          if c not in base_module_build_config['resources']['dependency_zips']
+          if c not in base_module_build_config['deps_info']['dependency_zips']
       ]
       extra_package_names = [
           c for c in extra_package_names if c not in
-          base_module_build_config['resources']['extra_package_names']
+          base_module_build_config['deps_info']['extra_package_names']
       ]
       extra_r_text_files = [
           c for c in extra_r_text_files if c not in
-          base_module_build_config['resources']['extra_r_text_files']
+          base_module_build_config['deps_info']['extra_r_text_files']
       ]
 
-    config['resources']['dependency_zips'] = dependency_zips
-    config['resources']['extra_package_names'] = extra_package_names
-    config['resources']['extra_r_text_files'] = extra_r_text_files
+    config['deps_info']['dependency_zips'] = dependency_zips
+    config['deps_info']['extra_package_names'] = extra_package_names
+    config['deps_info']['extra_r_text_files'] = extra_r_text_files
     if options.type == 'android_apk' and options.tested_apk_config:
-      config['resources']['arsc_package_name'] = (
+      config['deps_info']['arsc_package_name'] = (
           tested_apk_config['package_name'])
     if options.res_size_info:
-      config['resources']['size_info'] = options.res_size_info
+      config['deps_info']['res_size_info'] = options.res_size_info
 
   if is_apk_or_module_target:
     deps_dex_files = [c['dex_path'] for c in all_library_deps]
@@ -1446,38 +1433,43 @@
     assert len(base_module_configs) == 1, 'Must have exactly 1 base module!'
     deps_info['base_module_config'] = base_module_configs[0]['path']
 
+  # Map configs to classpath entries that should be included in their final dex.
+  classpath_entries_by_owning_config = collections.defaultdict(list)
+  extra_main_r_text_files = []
   if is_static_library_dex_provider_target:
     # Map classpath entries to configs that include them in their classpath.
     configs_by_classpath_entry = collections.defaultdict(list)
     static_lib_jar_paths = {}
-    for config_path, dep_config in (
-        static_library_dependent_configs_by_path.iteritems()):
+    for config_path, dep_config in (sorted(
+        static_library_dependent_configs_by_path.iteritems())):
       # For bundles, only the jar path and jni sources of the base module
       # are relevant for proguard. Should be updated when bundle feature
       # modules support JNI.
       base_config = dep_config
       if dep_config['type'] == 'android_app_bundle':
         base_config = GetDepConfig(dep_config['base_module_config'])
+      extra_main_r_text_files.append(base_config['r_text_path'])
       static_lib_jar_paths[config_path] = base_config['jar_path']
       all_configs.extend(dep_config['proguard_all_configs'])
       extra_proguard_classpath_jars.extend(
           dep_config['proguard_classpath_jars'])
       all_java_sources.extend(base_config['jni']['all_source'])
+
+      # The srcjars containing the generated R.java files are excluded for APK
+      # targets the use static libraries, so we add them here to ensure the
+      # union of resource IDs are available in the static library APK.
+      for r_text in base_config['extra_r_text_files']:
+        if r_text not in extra_r_text_files:
+          extra_r_text_files.append(r_text)
+      for package in base_config['extra_package_names']:
+        if package not in extra_package_names:
+          extra_package_names.append(package)
       for cp_entry in dep_config['java_runtime_classpath']:
-        # The APK Java targets for the static library dependent targets will
-        # have some of the same classes (R.java) due to shared resource
-        # dependencies. To avoid Proguard failures due to duplicate classes, we
-        # merge the APK jars into the static library's jar_path as a
-        # preprocessing build step.
-        if cp_entry != base_config['jar_path']:
-          configs_by_classpath_entry[cp_entry].append(config_path)
+        configs_by_classpath_entry[cp_entry].append(config_path)
 
     for cp_entry in java_full_classpath:
       configs_by_classpath_entry[cp_entry].append(options.build_config)
 
-    # Map configs to classpath entries that should be included in their final
-    # dex.
-    classpath_entries_by_owning_config = collections.defaultdict(list)
     for cp_entry, candidate_configs in configs_by_classpath_entry.iteritems():
       config_path = (candidate_configs[0]
                      if len(candidate_configs) == 1 else options.build_config)
@@ -1485,32 +1477,16 @@
       java_full_classpath.append(cp_entry)
 
     java_full_classpath = sorted(set(java_full_classpath))
-    deps_info['static_library_dependent_classpath_configs'] = {
-        path: sorted(set(classpath))
-        for path, classpath in classpath_entries_by_owning_config.iteritems()
-    }
 
-    # resource_ids_provider's jar must go first to ensure the correct R.java
-    # IDs are used.
-    ordered_static_lib_jar_paths = []
-    if options.resource_ids_provider:
-      assert (options.resource_ids_provider in options.
-              static_library_dependent_configs), (
-                  '--resource-ids-provider must be in '
-                  '--static-library-dependent-configs')
-      ordered_static_lib_jar_paths.append(
-          static_lib_jar_paths[options.resource_ids_provider])
-
-    ordered_static_lib_jar_paths.extend(
-        x for x in static_lib_jar_paths.itervalues()
-        if x not in ordered_static_lib_jar_paths)
-    ordered_static_lib_jar_paths.append(options.static_library_jar_path)
-    deps_info[
-        'static_library_dependent_apk_jars'] = ordered_static_lib_jar_paths
-    deps_info['static_library_proguard_mapping_output_paths'] = [
-        d['proguard_mapping_path']
-        for d in static_library_dependent_configs_by_path.itervalues()
-    ]
+  deps_info['static_library_proguard_mapping_output_paths'] = sorted([
+      d['proguard_mapping_path']
+      for d in static_library_dependent_configs_by_path.itervalues()
+  ])
+  deps_info['static_library_dependent_classpath_configs'] = {
+      path: sorted(set(classpath))
+      for path, classpath in classpath_entries_by_owning_config.iteritems()
+  }
+  deps_info['extra_main_r_text_files'] = sorted(extra_main_r_text_files)
 
   if is_apk_or_module_target or options.type in ('group', 'java_library',
                                                  'junit_binary'):
diff --git a/build/android/pylib/linker/test_case.py b/build/android/pylib/linker/test_case.py
index f94f1a8..47933c2 100644
--- a/build/android/pylib/linker/test_case.py
+++ b/build/android/pylib/linker/test_case.py
@@ -27,11 +27,7 @@
        Host-driven tests have also been tried, but since they're really
        sub-classes of instrumentation tests, they didn't work well either.
 
-   To build and run the linker tests, do the following:
-
-     ninja -C out/Debug chromium_linker_test_apk
-     out/Debug/bin/run_chromium_linker_test_apk
-
+   To build and run, refer to android_linker_testing.md.
 """
 # pylint: disable=R0201
 
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index a9273aa..abf6922 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -309,7 +309,7 @@
     }
     if (defined(invoker.r_text)) {
       args += [
-        "--r-text",
+        "--r-text-path",
         rebase_path(invoker.r_text, root_build_dir),
       ]
     }
@@ -328,9 +328,9 @@
           rebase_path(invoker.proto_resources_path, root_build_dir)
       args += [ "--apk-proto-resources=$_rebased_proto_resources" ]
     }
-    if (defined(invoker.module_rtxt_path)) {
-      _rebased_rtxt_path = rebase_path(invoker.module_rtxt_path, root_build_dir)
-      args += [ "--module-rtxt-path=$_rebased_rtxt_path" ]
+    if (defined(invoker.r_text_path)) {
+      _rebased_rtxt_path = rebase_path(invoker.r_text_path, root_build_dir)
+      args += [ "--r-text-path=$_rebased_rtxt_path" ]
     }
     if (defined(invoker.module_pathmap_path)) {
       _rebased_pathmap_path =
@@ -456,11 +456,6 @@
       args += [ "--proguard-configs=$_rebased_proguard_configs" ]
     }
     if (defined(invoker.static_library_dependent_targets)) {
-      assert(defined(invoker.static_library_jar_path))
-      args += [
-        "--static-library-jar-path",
-        rebase_path(invoker.static_library_jar_path, root_build_dir),
-      ]
       _dependent_configs = []
       foreach(_target, invoker.static_library_dependent_targets) {
         _target_name = _target.name
@@ -1754,6 +1749,10 @@
       if (defined(invoker.manifest_package)) {
         args += [ "--manifest-package=${invoker.manifest_package}" ]
       }
+
+      if (defined(invoker.max_sdk_version)) {
+        args += [ "--max-sdk-version=${invoker.max_sdk_version}" ]
+      }
     }
   }
 
@@ -1860,9 +1859,9 @@
         "--include-resources=@FileArg($_rebased_build_config:android:sdk_jars)",
         "--aapt-path",
         rebase_path(_android_aapt_path, root_build_dir),
-        "--dependencies-res-zips=@FileArg($_rebased_build_config:resources:dependency_zips)",
-        "--extra-res-packages=@FileArg($_rebased_build_config:resources:extra_package_names)",
-        "--extra-r-text-files=@FileArg($_rebased_build_config:resources:extra_r_text_files)",
+        "--dependencies-res-zips=@FileArg($_rebased_build_config:deps_info:dependency_zips)",
+        "--extra-res-packages=@FileArg($_rebased_build_config:deps_info:extra_package_names)",
+        "--extra-r-text-files=@FileArg($_rebased_build_config:deps_info:extra_r_text_files)",
       ]
 
       if (defined(invoker.android_manifest)) {
@@ -2108,9 +2107,10 @@
         "--include-resources=@FileArg($_rebased_build_config:android:sdk_jars)",
         "--aapt2-path",
         rebase_path(android_sdk_tools_bundle_aapt2, root_build_dir),
-        "--dependencies-res-zips=@FileArg($_rebased_build_config:resources:dependency_zips)",
-        "--extra-res-packages=@FileArg($_rebased_build_config:resources:extra_package_names)",
-        "--extra-r-text-files=@FileArg($_rebased_build_config:resources:extra_r_text_files)",
+        "--dependencies-res-zips=@FileArg($_rebased_build_config:deps_info:dependency_zips)",
+        "--extra-res-packages=@FileArg($_rebased_build_config:deps_info:extra_package_names)",
+        "--extra-r-text-files=@FileArg($_rebased_build_config:deps_info:extra_r_text_files)",
+        "--extra-main-r-text-files=@FileArg($_rebased_build_config:deps_info:extra_main_r_text_files)",
         "--min-sdk-version=${invoker.min_sdk_version}",
         "--target-sdk-version=${invoker.target_sdk_version}",
       ]
@@ -2337,6 +2337,7 @@
         args += [ "--use-resource-ids-path=$_rebased_ids_path" ]
         deps += [ _compile_res_dep ]
       }
+
       if (defined(invoker.max_sdk_version)) {
         _max_sdk_version = invoker.max_sdk_version
         args += [ "--max-sdk-version=$_max_sdk_version" ]
@@ -2389,7 +2390,7 @@
           args += [
             "--r-text-whitelist",
             rebase_path(invoker.shared_resources_whitelist, root_build_dir),
-            "--r-text",
+            "--r-text-path",
             rebase_path(invoker.r_text_out_path, root_build_dir),
           ]
         }
@@ -2454,7 +2455,7 @@
           args += [
             "--jar-files=@FileArg($_rebased_build_config:deps_info:unprocessed_jar_path)",
             "--jar-files=@FileArg($_rebased_build_config:deps_info:javac_full_classpath)",
-            "--in-res-info-path=@FileArg($_rebased_build_config:resources:size_info)",
+            "--in-res-info-path=@FileArg($_rebased_build_config:deps_info:res_size_info)",
             "--assets=@FileArg($_rebased_build_config:assets)",
             "--uncompressed-assets=@FileArg($_rebased_build_config:uncompressed_assets)",
           ]
@@ -3038,8 +3039,6 @@
   #    java_files is empty. If not
   #  jar_path: Optional path to a prebuilt .jar file for this target.
   #    Mutually exclusive with java_files and srcjar_deps.
-  #  intermediate_jar_path: Optional path to the output .jar file. If used,
-  #    final_jar_path must be created by another target.
   #  final_jar_path: Optional path to the final output .jar file (after
   #    processing). If not provided, the output will go under
   #    $root_build_dir/lib.java/
@@ -3125,8 +3124,8 @@
   #  proto_resources_path: The path of an zip archive containing the APK's
   #    resources compiled to the protocol buffer format (instead of regular
   #    binary xml + resources.arsc).
-  #  module_rtxt_path: The path of the R.txt file generated when compiling the
-  #    resources for the bundle module.
+  #  r_text_path: The path of the R.txt file generated when compiling the
+  #    resources for this target.
   #  module_pathmap_path: The path of the pathmap file generated when compiling
   #    the resources for the bundle module, if path shortening is enabled.
   #  base_whitelist_rtxt_path: The path of the R.txt file containing the
@@ -3213,9 +3212,6 @@
       # for the ijar as well, but this is only used for APK targets where
       # the ijar path isn't actually used.
       _build_config_jar_path = _final_jar_path
-      if (defined(invoker.intermediate_jar_path)) {
-        _final_jar_path = invoker.intermediate_jar_path
-      }
       _final_ijar_path =
           get_path_info(_final_jar_path, "dir") + "/" +
           get_path_info(_final_jar_path, "name") + ".interface.jar"
@@ -3334,6 +3330,7 @@
                                "proguard_configs",
                                "proguard_enabled",
                                "proguard_mapping_path",
+                               "r_text_path",
                                "secondary_abi_loadable_modules",
                                "type",
                              ])
@@ -3370,7 +3367,6 @@
                                  "base_module_target",
                                  "is_base_module",
                                  "module_pathmap_path",
-                                 "module_rtxt_path",
                                  "proto_resources_path",
                                ])
       }
@@ -3385,9 +3381,6 @@
       bypass_platform_checks = defined(invoker.bypass_platform_checks) &&
                                invoker.bypass_platform_checks
 
-      if (defined(invoker.intermediate_jar_path)) {
-        static_library_jar_path = invoker.intermediate_jar_path
-      }
       if (defined(_final_jar_path)) {
         jar_path = _build_config_jar_path
         ijar_path = _final_ijar_path
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index bcf645c..4481c20 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -1757,8 +1757,8 @@
         rebase_path(depfile, root_build_dir),
         "--output",
         rebase_path(invoker.output, root_build_dir),
-        "--dependencies-res-zips=@FileArg($_rebased_build_config:resources:dependency_zips)",
-        "--r-text-files=@FileArg($_rebased_build_config:resources:extra_r_text_files)",
+        "--dependencies-res-zips=@FileArg($_rebased_build_config:deps_info:dependency_zips)",
+        "--r-text-files=@FileArg($_rebased_build_config:deps_info:extra_r_text_files)",
         "--proguard-configs=@FileArg($_rebased_build_config:deps_info:proguard_all_configs)",
       ]
       if (_direct_deps_only) {
@@ -2308,7 +2308,6 @@
     _is_static_library_provider =
         defined(invoker.static_library_dependent_targets) && _proguard_enabled
     if (_is_static_library_provider) {
-      _static_library_apk_java_target_output = _jar_path
       _static_library_sync_dex_path =
           "$_gen_dir/static_library_synchronized_proguard.classes.dex.zip"
       _resource_ids_provider_deps = []
@@ -2344,7 +2343,11 @@
         "$target_gen_dir/${_template_name}_manifest/AndroidManifest.xml"
     _merge_manifest_target = "${_template_name}__merge_manifests"
     merge_manifests(_merge_manifest_target) {
-      forward_variables_from(invoker, [ "manifest_package" ])
+      forward_variables_from(invoker,
+                             [
+                               "manifest_package",
+                               "max_sdk_version",
+                             ])
       input_manifest = _android_root_manifest
       output_manifest = _android_manifest
       build_config = _build_config
@@ -2457,13 +2460,21 @@
                _android_sdk_dep,
              ]
 
+      # The static library uses the R.txt files generated by the
+      # static_library_dependent_targets when generating the final R.java file.
+      if (_is_static_library_provider) {
+        foreach(_dep, invoker.static_library_dependent_targets) {
+          deps += [ "${_dep.name}__compile_resources" ]
+        }
+      }
+
       if (defined(invoker.apk_under_test)) {
         # Set the arsc package name to match the apk_under_test package name
         # So that test resources can references under_test resources via
         # @type/name syntax.
         r_java_root_package_name = "test"
         arsc_package_name =
-            "@FileArg($_rebased_build_config:resources:arsc_package_name)"
+            "@FileArg($_rebased_build_config:deps_info:arsc_package_name)"
 
         # Passing in the --emit-ids mapping will cause aapt2 to assign resources
         # IDs that do not conflict with those from apk_under_test.
@@ -2572,7 +2583,20 @@
       }
     }
 
-    _srcjar_deps += [ ":$_compile_resources_target" ]
+    # The static library will provide all R.java files, but we still need to
+    # have a dep on the compile_resources() target from the java_library_impl()
+    # target below. Using a group to rename the target avoids the naming
+    # restrictions described in crbug.com/908819.
+    if (_uses_static_library) {
+      group("${_compile_resources_target}__compile_only_dep") {
+        public_deps = [
+          ":$_compile_resources_target",
+        ]
+      }
+      _deps += [ ":${_compile_resources_target}__compile_only_dep" ]
+    } else {
+      _srcjar_deps += [ ":$_compile_resources_target" ]
+    }
 
     if (_native_libs_deps != [] || _secondary_abi_native_libs_deps != []) {
       _enable_chromium_linker_tests = false
@@ -2769,24 +2793,19 @@
       } else {
         type = "android_apk"
       }
+      r_text_path = _compile_resources_rtxt_out
       main_target_name = _template_name
       supports_android = true
       requires_android = true
       min_sdk_version = _min_sdk_version
       deps = _deps
-
       srcjar_deps = _srcjar_deps
       final_jar_path = _jar_path
-      if (_is_static_library_provider) {
-        intermediate_jar_path = "$_base_path.intermediate.jar"
-        final_jar_path = _static_library_apk_java_target_output
-      }
       dex_path = _lib_dex_path
       final_dex_path = _final_dex_path
 
       if (_is_bundle_module) {
         proto_resources_path = _proto_resources_path
-        module_rtxt_path = _compile_resources_rtxt_out
         if (_optimize_resources) {
           proto_resources_path = _optimized_proto_resources_path
           if (_short_resource_paths) {
@@ -2855,38 +2874,6 @@
       }
     }
 
-    if (_is_static_library_provider) {
-      _static_library_apk_java_target = "${_template_name}__combine_apk_jars"
-
-      # Since some of the static_libary_dependent_targets may have overlapping
-      # resource dependencies, we can't include all jars created by the
-      # static_library_dependent_targets or else proguard will fail with
-      # duplicate class definitions. This step combines these jars into
-      # a single jar with duplicates ignored.
-      action_with_pydeps(_static_library_apk_java_target) {
-        script = "//build/android/gyp/zip.py"
-        deps = [
-          ":$_build_config_target",
-          ":$_java_target",
-        ]
-        foreach(_dep, invoker.static_library_dependent_targets) {
-          _target_label = get_label_info(_dep.name, "label_no_toolchain")
-          deps += [ "${_target_label}__java" ]
-        }
-        inputs = [
-          _build_config,
-        ]
-        outputs = [
-          _static_library_apk_java_target_output,
-        ]
-        args = [
-          "--input-zips=@FileArg($_rebased_build_config:deps_info:static_library_dependent_apk_jars)",
-          "--output",
-          rebase_path(_static_library_apk_java_target_output, root_build_dir),
-        ]
-      }
-    }
-
     if (_proguard_enabled && _uses_static_library) {
       _final_dex_target_dep = "${invoker.static_library_provider}__dexsplitter"
     } else if (!(_is_bundle_module && _proguard_enabled)) {
@@ -2909,9 +2896,6 @@
           ":$_build_config_target",
           ":$_java_target",
         ]
-        if (_is_static_library_provider) {
-          deps += [ ":$_static_library_apk_java_target" ]
-        }
         if (_proguard_enabled) {
           forward_variables_from(invoker, [ "proguard_jar_path" ])
           deps += _deps + [ ":$_compile_resources_target" ]
@@ -2927,6 +2911,13 @@
         }
 
         if (_is_static_library_provider) {
+          # The list of input jars is already recorded in the .build_config, but
+          # we need to explicitly add the java deps here to ensure they're
+          # available to be used as inputs to the dex step.
+          foreach(_dep, invoker.static_library_dependent_targets) {
+            _target_label = get_label_info(_dep.name, "label_no_toolchain")
+            deps += [ "${_target_label}__java" ]
+          }
           output = _static_library_sync_dex_path
           is_static_library = true
         } else {
@@ -4506,6 +4497,11 @@
     group("${target_name}__java") {
       deps = _sync_module_java_targets
     }
+    group("${target_name}__compile_resources") {
+      deps = [
+        "${invoker.base_module_target}__compile_resources",
+      ]
+    }
 
     _build_config = "$target_gen_dir/${target_name}.build_config"
     _rebased_build_config = rebase_path(_build_config, root_build_dir)
@@ -4712,7 +4708,8 @@
       if (_enable_language_splits) {
         args += [
           "--base-whitelist-rtxt-path=@FileArg(" + "${_rebased_base_module_build_config}:deps_info:base_whitelist_rtxt_path)",
-          "--base-module-rtxt-path=@FileArg(" + "${_rebased_base_module_build_config}:deps_info:module_rtxt_path)",
+          "--base-module-rtxt-path=@FileArg(" +
+              "${_rebased_base_module_build_config}:deps_info:r_text_path)",
         ]
       }
 
@@ -4722,7 +4719,7 @@
           "--uncompressed-assets=@FileArg(" +
               "$_rebased_build_config:uncompressed_assets)",
           "--rtxt-in-paths=@FileArg(" +
-              "$_rebased_build_config:deps_info:module_rtxt_path)",
+              "$_rebased_build_config:deps_info:r_text_path)",
           "--pathmap-in-paths=@FileArg(" +
               "$_rebased_build_config:deps_info:module_pathmap_path)",
         ]
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 28ca2a5..7afac95 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8908254478258311552
\ No newline at end of file
+8908200315658417376
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 097742f..be0f203 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8908254542434342448
\ No newline at end of file
+8908199400534799360
\ No newline at end of file
diff --git a/buildtools/ensure_gn_version.py b/buildtools/ensure_gn_version.py
index 9c01f58..acb1c1d 100755
--- a/buildtools/ensure_gn_version.py
+++ b/buildtools/ensure_gn_version.py
@@ -18,6 +18,7 @@
 from __future__ import print_function
 
 import argparse
+import errno
 import io
 import os
 import re
@@ -67,14 +68,22 @@
   except subprocess.CalledProcessError as e:
     print('`%s` returned %d:\n%s' % (cmd_str, e.returncode, e.output))
     return 1
+  except OSError as e:
+    if e.errno != errno.ENOENT:
+      print('`%s` failed:\n%s' % (cmd_str, e))
+      return 1
+
+    # The tool doesn't exist, so redownload it.
+    out = ''
   except Exception as e:
-    print('`%s` failed:\n%s' % (cmd_str, e.message))
+    print('`%s` failed:\n%s' % (cmd_str, e))
     return 1
 
-  current_revision = re.findall(r'\((.*)\)', out)[0]
-  if desired_revision.startswith(current_revision):
-    # We're on the right version, so we're done.
-    return 0
+  if out:
+    current_revision = re.findall(r'\((.*)\)', out)[0]
+    if desired_revision.startswith(current_revision):
+      # We're on the right version, so we're done.
+      return 0
 
   print("`%s` returned '%s', which wasn't what we were expecting."
           % (cmd_str, out.strip()))
@@ -96,7 +105,7 @@
     zf = zipfile.ZipFile(io.BytesIO(zipdata))
     zf.extract(member, os.path.join(BUILDTOOLS_DIR, dest_dir))
   except Exception as e:
-    print('Failed to extract the binary:\n%s\n' % e.msg)
+    print('Failed to extract the binary:\n%s\n' % e)
     return 1
 
   try:
diff --git a/cc/layers/mirror_layer_impl.cc b/cc/layers/mirror_layer_impl.cc
index b36f293..7a916bf 100644
--- a/cc/layers/mirror_layer_impl.cc
+++ b/cc/layers/mirror_layer_impl.cc
@@ -41,8 +41,8 @@
   viz::SharedQuadState* shared_quad_state =
       render_pass->CreateAndAppendSharedQuadState();
   PopulateScaledSharedQuadStateWithContentRects(
-      shared_quad_state, GetIdealContentsScale(), content_rect, content_rect,
-      contents_opaque);
+      shared_quad_state, mirrored_layer->GetIdealContentsScale(), content_rect,
+      content_rect, contents_opaque);
 
   AppendDebugBorderQuad(render_pass, content_rect, shared_quad_state,
                         append_quads_data);
@@ -75,7 +75,10 @@
 }
 
 gfx::Rect MirrorLayerImpl::GetEnclosingRectInTargetSpace() const {
-  return GetScaledEnclosingRectInTargetSpace(GetIdealContentsScale());
+  const LayerImpl* mirrored_layer =
+      layer_tree_impl()->LayerById(mirrored_layer_id_);
+  return GetScaledEnclosingRectInTargetSpace(
+      mirrored_layer->GetIdealContentsScale());
 }
 
 const char* MirrorLayerImpl::LayerTypeAsString() const {
diff --git a/cc/tiles/gpu_image_decode_cache_unittest.cc b/cc/tiles/gpu_image_decode_cache_unittest.cc
index 3e181f6..0ffd87e 100644
--- a/cc/tiles/gpu_image_decode_cache_unittest.cc
+++ b/cc/tiles/gpu_image_decode_cache_unittest.cc
@@ -298,15 +298,9 @@
                             true) {}
 };
 
-SkMatrix CreateMatrix(const SkSize& scale, bool is_decomposable) {
+SkMatrix CreateMatrix(const SkSize& scale) {
   SkMatrix matrix;
   matrix.setScale(scale.width(), scale.height());
-
-  if (!is_decomposable) {
-    // Perspective is not decomposable, add it.
-    matrix[SkMatrix::kMPersp0] = 0.1f;
-  }
-
   return matrix;
 }
 
@@ -420,6 +414,28 @@
                            gfx::ColorSpace::TransferID::LINEAR);
   }
 
+  DrawImage CreateDrawImageInternal(
+      const PaintImage& paint_image,
+      const SkMatrix& matrix = SkMatrix::I(),
+      gfx::ColorSpace* color_space = nullptr,
+      SkFilterQuality filter_quality = kMedium_SkFilterQuality,
+      SkIRect* src_rect = nullptr,
+      size_t frame_index = PaintImage::kDefaultFrameIndex) {
+    SkIRect src_rectangle;
+    gfx::ColorSpace cs;
+    if (!src_rect) {
+      src_rectangle =
+          SkIRect::MakeWH(paint_image.width(), paint_image.height());
+      src_rect = &src_rectangle;
+    }
+    if (!color_space) {
+      cs = DefaultColorSpace();
+      color_space = &cs;
+    }
+    return DrawImage(paint_image, *src_rect, filter_quality, matrix,
+                     frame_index, *color_space);
+  }
+
   GPUImageDecodeTestMockContextProvider* context_provider() {
     return context_provider_.get();
   }
@@ -524,22 +540,16 @@
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageSameImage) {
   auto cache = CreateCache();
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.5f, 1.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(1.5f, 1.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
   EXPECT_TRUE(result.task);
 
-  DrawImage another_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(1.5f, 1.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage another_draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(1.5f, 1.5f)));
   ImageDecodeCache::TaskResult another_result = cache->GetTaskForImageAndRef(
       another_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(another_result.need_unref);
@@ -555,13 +565,8 @@
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageSmallerScale) {
   auto cache = CreateCache();
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.5f, 1.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(1.5f, 1.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -571,10 +576,8 @@
   EXPECT_EQ(result.task->dependencies().size(), 1u);
   EXPECT_TRUE(result.task->dependencies()[0]);
 
-  DrawImage another_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage another_draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult another_result = cache->GetTaskForImageAndRef(
       another_draw_image, ImageDecodeCache::TracingInfo());
 
@@ -597,21 +600,15 @@
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageLowerQuality) {
   auto cache = CreateCache();
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  bool is_decomposable = true;
-  SkMatrix matrix = CreateMatrix(SkSize::Make(0.4f, 0.4f), is_decomposable);
-
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       kHigh_SkFilterQuality, matrix,
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  SkMatrix matrix = CreateMatrix(SkSize::Make(0.4f, 0.4f));
+  DrawImage draw_image = CreateDrawImageInternal(image, matrix);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
   EXPECT_TRUE(result.task);
 
-  DrawImage another_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()),
-      kLow_SkFilterQuality, matrix, PaintImage::kDefaultFrameIndex,
-      DefaultColorSpace());
+  DrawImage another_draw_image = CreateDrawImageInternal(
+      image, matrix, nullptr /* color_space */, kLow_SkFilterQuality);
   ImageDecodeCache::TaskResult another_result = cache->GetTaskForImageAndRef(
       another_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(another_result.need_unref);
@@ -626,25 +623,18 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageDifferentImage) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
   EXPECT_TRUE(first_result.task);
 
   PaintImage second_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage second_draw_image(
-      second_image,
-      SkIRect::MakeWH(second_image.width(), second_image.height()), quality,
-      CreateMatrix(SkSize::Make(0.25f, 0.25f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(
+      second_image, CreateMatrix(SkSize::Make(0.25f, 0.25f)));
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
@@ -662,14 +652,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageLargerScale) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
@@ -680,20 +666,15 @@
 
   cache->UnrefImage(first_draw_image);
 
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(first_image);
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
   EXPECT_TRUE(second_result.task);
   EXPECT_TRUE(first_result.task.get() != second_result.task.get());
 
-  DrawImage third_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage third_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult third_result = cache->GetTaskForImageAndRef(
       third_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(third_result.need_unref);
@@ -708,33 +689,23 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageLargerScaleNoReuse) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
   EXPECT_TRUE(first_result.task);
 
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(first_image);
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
   EXPECT_TRUE(second_result.task);
   EXPECT_TRUE(first_result.task.get() != second_result.task.get());
 
-  DrawImage third_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage third_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult third_result = cache->GetTaskForImageAndRef(
       third_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(third_result.need_unref);
@@ -752,14 +723,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageHigherQuality) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkMatrix matrix = CreateMatrix(SkSize::Make(0.4f, 0.4f), is_decomposable);
-
+  SkMatrix matrix = CreateMatrix(SkSize::Make(0.4f, 0.4f));
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      kLow_SkFilterQuality, matrix, PaintImage::kDefaultFrameIndex,
-      DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, matrix, nullptr /* color_space */, kLow_SkFilterQuality);
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
@@ -770,10 +737,8 @@
 
   cache->UnrefImage(first_draw_image);
 
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      kHigh_SkFilterQuality, matrix, PaintImage::kDefaultFrameIndex,
-      DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(
+      first_image, matrix, nullptr /* color_space */, kMedium_SkFilterQuality);
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
@@ -787,14 +752,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageAlreadyDecodedAndLocked) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -829,14 +789,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageAlreadyDecodedNotLocked) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -871,14 +826,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageAlreadyUploaded) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -902,14 +852,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageCanceledGetsNewTask) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -945,14 +890,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageCanceledWhileReffedGetsNewTask) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -992,14 +932,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageUploadCanceledButDecodeRun) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1021,14 +956,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, NoTaskForImageAlreadyFailedDecoding) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1051,14 +981,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetDecodedImageForDraw) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1085,14 +1010,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetLargeDecodedImageForDraw) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreateLargePaintImageForSoftwareFallback();
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(1.0f, 1.0f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1119,16 +1039,11 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetDecodedImageForDrawAtRasterDecode) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   cache->SetWorkingSetLimitsForTesting(0 /* max_bytes */, 0 /* max_items */);
 
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(1.0f, 1.0f)));
 
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -1155,14 +1070,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetDecodedImageForDrawLargerScale) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       kLow_SkFilterQuality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)),
+                              nullptr /* color_space */, kLow_SkFilterQuality);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1171,10 +1082,8 @@
   TestTileTaskRunner::ProcessTask(result.task->dependencies()[0].get());
   TestTileTaskRunner::ProcessTask(result.task.get());
 
-  DrawImage larger_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(1.5f, 1.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage larger_draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(1.5f, 1.5f)));
   ImageDecodeCache::TaskResult larger_result = cache->GetTaskForImageAndRef(
       larger_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(larger_result.need_unref);
@@ -1210,13 +1119,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetDecodedImageForDrawHigherQuality) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkMatrix matrix = CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable);
-
+  SkMatrix matrix = CreateMatrix(SkSize::Make(0.5f, 0.5f));
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       kLow_SkFilterQuality, matrix,
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, matrix, nullptr /* color_space */, kLow_SkFilterQuality);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1225,10 +1131,7 @@
   TestTileTaskRunner::ProcessTask(result.task->dependencies()[0].get());
   TestTileTaskRunner::ProcessTask(result.task.get());
 
-  DrawImage higher_quality_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()),
-      kHigh_SkFilterQuality, matrix, PaintImage::kDefaultFrameIndex,
-      DefaultColorSpace());
+  DrawImage higher_quality_draw_image = CreateDrawImageInternal(image, matrix);
   ImageDecodeCache::TaskResult hq_result = cache->GetTaskForImageAndRef(
       higher_quality_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(hq_result.need_unref);
@@ -1263,14 +1166,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetDecodedImageForDrawNegative) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(-0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(-0.5f, 0.5f)));
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1301,15 +1199,11 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetLargeScaledDecodedImageForDraw) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreateLargePaintImageForSoftwareFallback(
       gfx::Size(GetLargeImageSize().width(), GetLargeImageSize().height() * 2));
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)),
+                              nullptr /* color_space */, kHigh_SkFilterQuality);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1342,17 +1236,11 @@
 
 TEST_P(GpuImageDecodeCacheTest, AtRasterUsedDirectlyIfSpaceAllows) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
   const gfx::Size test_image_size = GetNormalImageSize();
-
   cache->SetWorkingSetLimitsForTesting(0 /* max_bytes */, 0 /* max_items */);
 
   PaintImage image = CreatePaintImageInternal(test_image_size);
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
 
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -1386,16 +1274,11 @@
 TEST_P(GpuImageDecodeCacheTest,
        GetDecodedImageForDrawAtRasterDecodeMultipleTimes) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   cache->SetWorkingSetLimitsForTesting(0 /* max_bytes */, 0 /* max_items */);
 
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
 
   // Must hold context lock before calling GetDecodedImageForDraw /
   // DrawWithImageFinished.
@@ -1424,16 +1307,10 @@
 TEST_P(GpuImageDecodeCacheTest,
        GetLargeDecodedImageForDrawAtRasterDecodeMultipleTimes) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   cache->SetWorkingSetLimitsForTesting(0 /* max_bytes */, 0 /* max_items */);
 
   PaintImage image = CreateLargePaintImageForSoftwareFallback();
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
 
   // Must hold context lock before calling GetDecodedImageForDraw /
   // DrawWithImageFinished.
@@ -1463,14 +1340,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, ZeroSizedImagesAreSkipped) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.f, 0.f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.f, 0.f)));
 
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -1489,15 +1361,12 @@
 
 TEST_P(GpuImageDecodeCacheTest, NonOverlappingSrcRectImagesAreSkipped) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
   DrawImage draw_image(image,
                        SkIRect::MakeXYWH(image.width() + 1, image.height() + 1,
                                          image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.f, 1.f), is_decomposable),
+                       kMedium_SkFilterQuality,
+                       CreateMatrix(SkSize::Make(1.f, 1.f)),
                        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
   ImageDecodeCache::TaskResult result =
@@ -1517,14 +1386,11 @@
 
 TEST_P(GpuImageDecodeCacheTest, CanceledTasksDoNotCountAgainstBudget) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(
-      image, SkIRect::MakeXYWH(0, 0, image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(1.f, 1.f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  SkIRect src_rect = SkIRect::MakeXYWH(0, 0, image.width(), image.height());
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, CreateMatrix(SkSize::Make(1.f, 1.f)), nullptr /* color_space */,
+      kMedium_SkFilterQuality, &src_rect);
 
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -1543,14 +1409,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, ShouldAggressivelyFreeResources) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   {
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
@@ -1604,15 +1465,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, OrphanedImagesFreeOnReachingZeroRefs) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   // Create a downscaled image.
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
@@ -1624,10 +1480,8 @@
 
   // Create a larger version of |first_image|, this should immediately free the
   // memory used by |first_image| for the smaller scale.
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(1.0f, 1.0f)));
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
@@ -1652,15 +1506,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, OrphanedZeroRefImagesImmediatelyDeleted) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   // Create a downscaled image.
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
@@ -1676,10 +1525,7 @@
 
   // Create a larger version of |first_image|, this should immediately free the
   // memory used by |first_image| for the smaller scale.
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(first_image);
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
@@ -1699,14 +1545,11 @@
 TEST_P(GpuImageDecodeCacheTest, QualityCappedAtMedium) {
   auto cache = CreateCache();
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  bool is_decomposable = true;
-  SkMatrix matrix = CreateMatrix(SkSize::Make(0.4f, 0.4f), is_decomposable);
+  SkMatrix matrix = CreateMatrix(SkSize::Make(0.4f, 0.4f));
 
   // Create an image with kLow_FilterQuality.
-  DrawImage low_draw_image(image,
-                           SkIRect::MakeWH(image.width(), image.height()),
-                           kLow_SkFilterQuality, matrix,
-                           PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage low_draw_image = CreateDrawImageInternal(
+      image, matrix, nullptr /* color_space */, kLow_SkFilterQuality);
   ImageDecodeCache::TaskResult low_result = cache->GetTaskForImageAndRef(
       low_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(low_result.need_unref);
@@ -1714,21 +1557,16 @@
 
   // Get the same image at kMedium_SkFilterQuality. We can't re-use low, so we
   // should get a new task/ref.
-  DrawImage medium_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()),
-      kMedium_SkFilterQuality, matrix, PaintImage::kDefaultFrameIndex,
-      DefaultColorSpace());
+  DrawImage medium_draw_image = CreateDrawImageInternal(image);
   ImageDecodeCache::TaskResult medium_result = cache->GetTaskForImageAndRef(
       medium_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(medium_result.need_unref);
   EXPECT_TRUE(medium_result.task.get());
   EXPECT_FALSE(low_result.task.get() == medium_result.task.get());
 
-  // Get the same image at kHigh_FilterQuality. We should re-use medium.
-  DrawImage high_quality_draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()),
-      kHigh_SkFilterQuality, matrix, PaintImage::kDefaultFrameIndex,
-      DefaultColorSpace());
+  // Get the same image at kHigh_SkFilterQuality. We should re-use medium.
+  DrawImage high_quality_draw_image = CreateDrawImageInternal(
+      image, matrix, nullptr /* color_space */, kHigh_SkFilterQuality);
   ImageDecodeCache::TaskResult high_quality_result =
       cache->GetTaskForImageAndRef(high_quality_draw_image,
                                    ImageDecodeCache::TracingInfo());
@@ -1749,15 +1587,9 @@
 // cache entry creation doesn't cause a buffer overflow/crash.
 TEST_P(GpuImageDecodeCacheTest, GetDecodedImageForDrawMipUsageChange) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   // Create an image decode task and cache entry that does not need mips.
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1776,10 +1608,8 @@
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
 
   // Do an at-raster decode of the above image that *does* require mips.
-  DrawImage draw_image_mips(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(0.6f, 0.6f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image_mips =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.6f, 0.6f)));
   DecodedDrawImage decoded_draw_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image_mips));
   cache->DrawWithImageFinished(draw_image_mips, decoded_draw_image);
@@ -1787,13 +1617,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, OutOfRasterDecodeTask) {
   auto cache = CreateCache();
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  bool is_decomposable = true;
-  SkMatrix matrix = CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable);
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       kLow_SkFilterQuality, matrix,
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  SkMatrix matrix = CreateMatrix(SkSize::Make(1.0f, 1.0f));
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, matrix, nullptr /* color_space */, kLow_SkFilterQuality);
 
   ImageDecodeCache::TaskResult result =
       cache->GetOutOfRasterDecodeTaskForImageAndRef(draw_image);
@@ -1812,16 +1639,10 @@
 TEST_P(GpuImageDecodeCacheTest, ZeroCacheNormalWorkingSet) {
   SetCachedTexturesLimit(0);
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   // Add an image to the cache-> Due to normal working set, this should produce
   // a task and a ref.
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -1865,19 +1686,13 @@
   // Cache will fit one image.
   SetCachedTexturesLimit(1);
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
 
   PaintImage image2 = CreatePaintImageInternal(GetNormalImageSize());
   DrawImage draw_image2(
-      image2, SkIRect::MakeWH(image2.width(), image2.height()), quality,
-      CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
+      image2, SkIRect::MakeWH(image2.width(), image2.height()),
+      kMedium_SkFilterQuality, CreateMatrix(SkSize::Make(1.0f, 1.0f)),
       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
 
   // Add an image to the cache and un-ref it.
@@ -1947,15 +1762,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, ClearCache) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   for (int i = 0; i < 10; ++i) {
     PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-    DrawImage draw_image(
-        image, SkIRect::MakeWH(image.width(), image.height()), quality,
-        CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+    DrawImage draw_image = CreateDrawImageInternal(image);
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
     EXPECT_TRUE(result.need_unref);
@@ -1977,15 +1786,9 @@
 
 TEST_P(GpuImageDecodeCacheTest, ClearCacheInUse) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   // Create an image but keep it reffed so it can't be immediately freed.
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -2012,36 +1815,27 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForImageDifferentColorSpace) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   gfx::ColorSpace color_space_a = gfx::ColorSpace::CreateSRGB();
   gfx::ColorSpace color_space_b = gfx::ColorSpace::CreateXYZD50();
 
   PaintImage first_image = CreatePaintImageInternal(gfx::Size(100, 100));
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, color_space_a);
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space_a);
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
   EXPECT_TRUE(first_result.task);
 
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, color_space_b);
+  DrawImage second_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space_b);
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
   EXPECT_TRUE(second_result.task);
   EXPECT_TRUE(first_result.task.get() != second_result.task.get());
 
-  DrawImage third_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, color_space_a);
+  DrawImage third_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space_a);
   ImageDecodeCache::TaskResult third_result = cache->GetTaskForImageAndRef(
       third_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(third_result.need_unref);
@@ -2059,15 +1853,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, GetTaskForLargeImageNonSRGBColorSpace) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
   gfx::ColorSpace color_space = gfx::ColorSpace::CreateXYZD50();
-
   PaintImage image = CreateLargePaintImageForSoftwareFallback();
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, color_space);
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -2081,7 +1870,6 @@
 
 TEST_P(GpuImageDecodeCacheTest, CacheDecodesExpectedFrames) {
   auto cache = CreateCache();
-
   std::vector<FrameMetadata> frames = {
       FrameMetadata(true, base::TimeDelta::FromMilliseconds(2)),
       FrameMetadata(true, base::TimeDelta::FromMilliseconds(3)),
@@ -2103,12 +1891,10 @@
 
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
 
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
+  SkFilterQuality quality = kMedium_SkFilterQuality;
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       1u, DefaultColorSpace());
+                       quality, CreateMatrix(SkSize::Make(1.0f, 1.0f)), 1u,
+                       DefaultColorSpace());
   auto decoded_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
   ASSERT_TRUE(decoded_image.image());
@@ -2135,8 +1921,7 @@
   ASSERT_LT(subset_height, test_image_size.height());
   DrawImage subset_draw_image(
       image, SkIRect::MakeWH(subset_width, subset_height), quality,
-      CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable), 3u,
-      DefaultColorSpace());
+      CreateMatrix(SkSize::Make(1.0f, 1.0f)), 3u, DefaultColorSpace());
   decoded_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(subset_draw_image));
   ASSERT_TRUE(decoded_image.image());
@@ -2148,15 +1933,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, OrphanedDataCancelledWhileReplaced) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   // Create a downscaled image.
   PaintImage first_image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage first_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage first_draw_image = CreateDrawImageInternal(
+      first_image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   ImageDecodeCache::TaskResult first_result = cache->GetTaskForImageAndRef(
       first_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(first_result.need_unref);
@@ -2167,10 +1947,7 @@
 
   // Create a larger version of |first_image|, this should immediately free
   // the memory used by |first_image| for the smaller scale.
-  DrawImage second_draw_image(
-      first_image, SkIRect::MakeWH(first_image.width(), first_image.height()),
-      quality, CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage second_draw_image = CreateDrawImageInternal(first_image);
   ImageDecodeCache::TaskResult second_result = cache->GetTaskForImageAndRef(
       second_draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(second_result.need_unref);
@@ -2203,15 +1980,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, AlreadyBudgetedImagesAreNotAtRaster) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  const SkFilterQuality quality = kHigh_SkFilterQuality;
   const gfx::Size test_image_size = GetNormalImageSize();
 
   PaintImage image = CreatePaintImageInternal(test_image_size);
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   const size_t bytes_for_test_image =
       GetBytesNeededForSingleImage(test_image_size);
 
@@ -2244,8 +2016,6 @@
 
 TEST_P(GpuImageDecodeCacheTest, ImageBudgetingByCount) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  const SkFilterQuality quality = kHigh_SkFilterQuality;
   const gfx::Size test_image_size = GetNormalImageSize();
 
   // Allow a single image by count. Use a high byte limit as we want to test the
@@ -2255,10 +2025,7 @@
   cache->SetWorkingSetLimitsForTesting(
       bytes_for_test_image * 100 /* max_bytes */, 1u /* max_items */);
   PaintImage image = CreatePaintImageInternal(test_image_size);
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
 
   // The image counts against our budget.
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
@@ -2268,11 +2035,9 @@
   EXPECT_TRUE(decoded_draw_image.is_budgeted());
 
   // Try another image, it shouldn't be budgeted and should be at-raster.
-  DrawImage second_draw_image(
-      CreatePaintImageInternal(GetNormalImageSize()),
-      SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  PaintImage second_paint_image =
+      CreatePaintImageInternal(GetNormalImageSize());
+  DrawImage second_draw_image = CreateDrawImageInternal(second_paint_image);
 
   // Should be at raster.
   ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
@@ -2291,15 +2056,10 @@
 
 TEST_P(GpuImageDecodeCacheTest, ImageBudgetingBySize) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  const SkFilterQuality quality = kHigh_SkFilterQuality;
   const gfx::Size test_image_size = GetNormalImageSize();
 
   PaintImage image = CreatePaintImageInternal(test_image_size);
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
 
   const size_t bytes_for_test_image =
       GetBytesNeededForSingleImage(test_image_size);
@@ -2317,11 +2077,8 @@
   EXPECT_TRUE(decoded_draw_image.is_budgeted());
 
   // Try another image, it shouldn't be budgeted and should be at-raster.
-  DrawImage second_draw_image(
-      CreatePaintImageInternal(test_image_size),
-      SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  PaintImage test_paint_image = CreatePaintImageInternal(test_image_size);
+  DrawImage second_draw_image = CreateDrawImageInternal(test_paint_image);
 
   // Should be at raster.
   ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
@@ -2341,15 +2098,11 @@
 TEST_P(GpuImageDecodeCacheTest,
        ColorConversionDuringDecodeForLargeImageNonSRGBColorSpace) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  const SkFilterQuality quality = kHigh_SkFilterQuality;
   gfx::ColorSpace color_space = gfx::ColorSpace::CreateXYZD50();
 
   PaintImage image = CreateLargePaintImageForSoftwareFallback();
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, color_space);
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -2396,15 +2149,11 @@
 TEST_P(GpuImageDecodeCacheTest,
        ColorConversionDuringUploadForSmallImageNonSRGBColorSpace) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  const SkFilterQuality quality = kHigh_SkFilterQuality;
   gfx::ColorSpace color_space = gfx::ColorSpace::CreateDisplayP3D65();
 
   PaintImage image = CreatePaintImageInternal(gfx::Size(11, 12));
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, color_space);
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -2449,14 +2198,9 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  const SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage image = CreateBitmapImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
   DecodedDrawImage decoded_draw_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
@@ -2474,14 +2218,9 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage image = CreateBitmapImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   auto result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -2498,14 +2237,9 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage image = CreateBitmapImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(image);
   auto result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -2526,14 +2260,10 @@
   const bool should_cache_sw_image =
       cache->SupportsColorSpaceConversion() && !use_transfer_cache_;
 
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
-
   PaintImage image = CreateBitmapImageInternal(GetLargeImageSize());
-  DrawImage draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, gfx::ColorSpace::CreateDisplayP3D65());
+  gfx::ColorSpace color_space = gfx::ColorSpace::CreateDisplayP3D65();
+  DrawImage draw_image = CreateDrawImageInternal(
+      image, CreateMatrix(SkSize::Make(1.0f, 1.0f)), &color_space);
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
   DecodedDrawImage decoded_draw_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
@@ -2545,9 +2275,8 @@
   auto sw_image = cache->GetSWImageDecodeForTesting(draw_image);
   ASSERT_EQ(!!sw_image, should_cache_sw_image);
   if (should_cache_sw_image) {
-    EXPECT_TRUE(SkColorSpace::Equals(
-        sw_image->colorSpace(),
-        gfx::ColorSpace::CreateDisplayP3D65().ToSkColorSpace().get()));
+    EXPECT_TRUE(SkColorSpace::Equals(sw_image->colorSpace(),
+                                     color_space.ToSkColorSpace().get()));
   }
 }
 
@@ -2557,14 +2286,10 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage image = CreateBitmapImageInternal(GetNormalImageSize());
-  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
   DecodedDrawImage decoded_draw_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
@@ -2575,8 +2300,6 @@
 
 TEST_P(GpuImageDecodeCacheTest, KeepOnlyLast2ContentIds) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
   const PaintImage::Id paint_image_id = PaintImage::GetNextId();
@@ -2586,10 +2309,8 @@
   for (int i = 0; i < 10; ++i) {
     PaintImage image = CreatePaintImageInternal(
         GetNormalImageSize(), SkColorSpace::MakeSRGB(), paint_image_id);
-    DrawImage draw_image(
-        image, SkIRect::MakeWH(image.width(), image.height()), quality,
-        CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
-        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+    DrawImage draw_image =
+        CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.5f, 0.5f)));
     DecodedDrawImage decoded_draw_image =
         EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
 
@@ -2625,8 +2346,6 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kMedium_SkFilterQuality;
 
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
   const SkISize full_size = SkISize::Make(100, 100);
@@ -2643,10 +2362,8 @@
                                .set_paint_image_generator(generator)
                                .TakePaintImage();
 
-  DrawImage draw_image(
-      paint_image, SkIRect::MakeWH(paint_image.width(), paint_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5, 0.5), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(
+      paint_image, CreateMatrix(SkSize::Make(0.5, 0.5)));
   DecodedDrawImage decoded_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
   const int expected_width =
@@ -2672,8 +2389,6 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kNone_SkFilterQuality;
 
   viz::ContextProvider::ScopedContextLock context_lock(context_provider());
   SkISize full_size = SkISize::Make(100, 100);
@@ -2690,10 +2405,9 @@
                                .set_paint_image_generator(generator)
                                .TakePaintImage();
 
-  DrawImage draw_image(
-      paint_image, SkIRect::MakeWH(paint_image.width(), paint_image.height()),
-      quality, CreateMatrix(SkSize::Make(0.5, 0.5), is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image =
+      CreateDrawImageInternal(paint_image, CreateMatrix(SkSize::Make(0.5, 0.5)),
+                              nullptr /* color_space */, kNone_SkFilterQuality);
   DecodedDrawImage decoded_image =
       EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
   ASSERT_TRUE(decoded_image.image());
@@ -2716,12 +2430,10 @@
                                       SkSize scale, gfx::ColorSpace color_space,
                                       bool should_have_mips) {
     auto cache = CreateCache();
-    bool is_decomposable = true;
 
     PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-    DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                         filter_quality, CreateMatrix(scale, is_decomposable),
-                         PaintImage::kDefaultFrameIndex, color_space);
+    DrawImage draw_image = CreateDrawImageInternal(
+        image, CreateMatrix(scale), &color_space, filter_quality);
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
     EXPECT_TRUE(result.need_unref);
@@ -2777,9 +2489,6 @@
   // Medium filter quality == mips
   decode_and_check_mips(kMedium_SkFilterQuality, SkSize::Make(0.6f, 0.6f),
                         DefaultColorSpace(), true);
-  // High filter quality == mips
-  decode_and_check_mips(kHigh_SkFilterQuality, SkSize::Make(0.6f, 0.6f),
-                        DefaultColorSpace(), true);
   // Color conversion preserves mips
   decode_and_check_mips(kMedium_SkFilterQuality, SkSize::Make(0.6f, 0.6f),
                         gfx::ColorSpace::CreateXYZD50(), true);
@@ -2787,17 +2496,12 @@
 
 TEST_P(GpuImageDecodeCacheTest, MipsAddedSubsequentDraw) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  auto filter_quality = kMedium_SkFilterQuality;
 
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
 
   // Create an image with no scaling. It will not have mips.
   {
-    DrawImage draw_image(
-        image, SkIRect::MakeWH(image.width(), image.height()), filter_quality,
-        CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+    DrawImage draw_image = CreateDrawImageInternal(image);
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
     EXPECT_TRUE(result.need_unref);
@@ -2839,10 +2543,8 @@
   // no new task (re-uses the existing image), but mips should have been
   // added.
   {
-    DrawImage draw_image(
-        image, SkIRect::MakeWH(image.width(), image.height()), filter_quality,
-        CreateMatrix(SkSize::Make(0.6f, 0.6f), is_decomposable),
-        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+    DrawImage draw_image =
+        CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.6f, 0.6f)));
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
     EXPECT_TRUE(result.need_unref);
@@ -2882,9 +2584,6 @@
 
 TEST_P(GpuImageDecodeCacheTest, MipsAddedWhileOriginalInUse) {
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  auto filter_quality = kMedium_SkFilterQuality;
-
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
 
   struct Decode {
@@ -2895,10 +2594,7 @@
 
   // Create an image with no scaling. It will not have mips.
   {
-    DrawImage draw_image(
-        image, SkIRect::MakeWH(image.width(), image.height()), filter_quality,
-        CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+    DrawImage draw_image = CreateDrawImageInternal(image);
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
     EXPECT_TRUE(result.need_unref);
@@ -2933,10 +2629,8 @@
 
   // Second decode with mips.
   {
-    DrawImage draw_image(
-        image, SkIRect::MakeWH(image.width(), image.height()), filter_quality,
-        CreateMatrix(SkSize::Make(0.6f, 0.6f), is_decomposable),
-        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+    DrawImage draw_image =
+        CreateDrawImageInternal(image, CreateMatrix(SkSize::Make(0.6f, 0.6f)));
     ImageDecodeCache::TaskResult result = cache->GetTaskForImageAndRef(
         draw_image, ImageDecodeCache::TracingInfo());
     EXPECT_TRUE(result.need_unref);
@@ -3019,15 +2713,14 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
   SkFilterQuality filter_quality = kMedium_SkFilterQuality;
   SkSize requires_decode_at_original_scale = SkSize::Make(0.8f, 0.8f);
 
   PaintImage image = CreatePaintImageInternal(GetNormalImageSize());
-  DrawImage draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), filter_quality,
-      CreateMatrix(requires_decode_at_original_scale, is_decomposable),
-      PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       filter_quality,
+                       CreateMatrix(requires_decode_at_original_scale),
+                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -3068,15 +2761,13 @@
     return;
   }
   auto cache = CreateCache();
-  bool is_decomposable = true;
   SkFilterQuality filter_quality = kMedium_SkFilterQuality;
   SkSize less_than_half_scale = SkSize::Make(0.45f, 0.45f);
 
   gfx::Size image_size = GetNormalImageSize();
   PaintImage image = CreatePaintImageInternal(image_size);
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       filter_quality,
-                       CreateMatrix(less_than_half_scale, is_decomposable),
+                       filter_quality, CreateMatrix(less_than_half_scale),
                        PaintImage::kDefaultFrameIndex, DefaultColorSpace());
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -3122,17 +2813,10 @@
   // We will create a texture that's at the maximum size the GPU says it can
   // support for uploads.
   auto cache = CreateCache();
-  bool is_decomposable = true;
-  SkFilterQuality quality = kHigh_SkFilterQuality;
 
   PaintImage almost_too_large_image =
       CreatePaintImageInternal(gfx::Size(max_texture_size_, max_texture_size_));
-  DrawImage draw_image(almost_too_large_image,
-                       SkIRect::MakeWH(almost_too_large_image.width(),
-                                       almost_too_large_image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
-                       PaintImage::kDefaultFrameIndex, DefaultColorSpace());
+  DrawImage draw_image = CreateDrawImageInternal(almost_too_large_image);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
 
@@ -3227,12 +2911,10 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
-  DrawImage draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, target_color_space);
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       quality, CreateMatrix(SkSize::Make(0.75f, 0.75f)),
+                       PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -3264,12 +2946,10 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
-  DrawImage draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, target_color_space);
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       quality, CreateMatrix(SkSize::Make(0.75f, 0.75f)),
+                       PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -3305,12 +2985,10 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
-  DrawImage draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, target_color_space);
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       quality, CreateMatrix(SkSize::Make(0.75f, 0.75f)),
+                       PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
@@ -3347,11 +3025,9 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
+                       quality, CreateMatrix(SkSize::Make(1.0f, 1.0f)),
                        PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetOutOfRasterDecodeTaskForImageAndRef(draw_image);
@@ -3373,11 +3049,9 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
+                       quality, CreateMatrix(SkSize::Make(0.5f, 0.5f)),
                        PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -3402,11 +3076,9 @@
   const PaintImage image = CreatePaintImageForDecodeAcceleration(
       image_size, image_color_space,
       false /* is_eligible_for_accelerated_decoding */);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
+                       quality, CreateMatrix(SkSize::Make(1.0f, 1.0f)),
                        PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -3431,11 +3103,9 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
-                       quality,
-                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
+                       quality, CreateMatrix(SkSize::Make(1.0f, 1.0f)),
                        PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
@@ -3459,12 +3129,10 @@
   ASSERT_TRUE(target_color_space.IsValid());
   const PaintImage image =
       CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
-  const bool is_decomposable = true;
   const SkFilterQuality quality = kHigh_SkFilterQuality;
-  DrawImage draw_image(
-      image, SkIRect::MakeWH(image.width(), image.height()), quality,
-      CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
-      PaintImage::kDefaultFrameIndex, target_color_space);
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       quality, CreateMatrix(SkSize::Make(0.75f, 0.75f)),
+                       PaintImage::kDefaultFrameIndex, target_color_space);
   ImageDecodeCache::TaskResult result =
       cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
   EXPECT_TRUE(result.need_unref);
diff --git a/cc/trees/layer_tree_host_pixeltest_mirror.cc b/cc/trees/layer_tree_host_pixeltest_mirror.cc
index cbd67e3..02ab6cc 100644
--- a/cc/trees/layer_tree_host_pixeltest_mirror.cc
+++ b/cc/trees/layer_tree_host_pixeltest_mirror.cc
@@ -17,14 +17,14 @@
 
 class LayerTreeHostMirrorPixelTest
     : public LayerTreePixelTest,
-      public ::testing::WithParamInterface<LayerTreeTest::RendererType> {
+      public ::testing::WithParamInterface<
+          ::testing::tuple<LayerTreeTest::RendererType, bool>> {
  protected:
-  RendererType renderer_type() { return GetParam(); }
+  RendererType renderer_type() { return std::get<0>(GetParam()); }
 
   void InitializeSettings(LayerTreeSettings* settings) override {
-    // MirrorLayer is only used by UI compositor; so, match its behavior by
-    // setting layer_transforms_should_scale_layer_contents to false.
-    settings->layer_transforms_should_scale_layer_contents = false;
+    settings->layer_transforms_should_scale_layer_contents =
+        std::get<1>(GetParam());
   }
 };
 
@@ -37,9 +37,11 @@
 #endif
 };
 
-INSTANTIATE_TEST_SUITE_P(,
-                         LayerTreeHostMirrorPixelTest,
-                         ::testing::ValuesIn(kRendererTypes));
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    LayerTreeHostMirrorPixelTest,
+    ::testing::Combine(::testing::ValuesIn(kRendererTypes),
+                       /*layer_transforms_scale_content=*/testing::Bool()));
 
 // Verifies that a mirror layer with a scale mirrors another layer correctly.
 TEST_P(LayerTreeHostMirrorPixelTest, MirrorLayer) {
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index 109bcf5..38c7314 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -424,7 +424,6 @@
       "//content/app/resources",
       "//content/public/common:service_names",
       "//crypto",
-      "//headless:headless_shell_browser_lib",
       "//net:net_resources",
       "//ppapi/buildflags",
       "//services/service_manager/embedder",
@@ -445,19 +444,16 @@
       "/DELAYLOAD:dbghelp.dll",
       "/DELAYLOAD:dhcpcsvc.dll",
       "/DELAYLOAD:dwmapi.dll",
-      "/DELAYLOAD:dwrite.dll",
       "/DELAYLOAD:dxgi.dll",
       "/DELAYLOAD:esent.dll",
       "/DELAYLOAD:gdi32.dll",
       "/DELAYLOAD:hid.dll",
       "/DELAYLOAD:imagehlp.dll",
       "/DELAYLOAD:imm32.dll",
-      "/DELAYLOAD:iphlpapi.dll",
       "/DELAYLOAD:msi.dll",
       "/DELAYLOAD:netapi32.dll",
       "/DELAYLOAD:ole32.dll",
       "/DELAYLOAD:oleacc.dll",
-      "/DELAYLOAD:oleaut32.dll",
       "/DELAYLOAD:ncrypt.dll",
       "/DELAYLOAD:propsys.dll",
       "/DELAYLOAD:psapi.dll",
@@ -474,16 +470,29 @@
       "/DELAYLOAD:wevtapi.dll",
       "/DELAYLOAD:winhttp.dll",
       "/DELAYLOAD:wininet.dll",
-      "/DELAYLOAD:winmm.dll",
       "/DELAYLOAD:winspool.drv",
       "/DELAYLOAD:wintrust.dll",
       "/DELAYLOAD:winusb.dll",
-      "/DELAYLOAD:ws2_32.dll",
       "/DELAYLOAD:wsock32.dll",
       "/DELAYLOAD:wtsapi32.dll",
       "/DELAYLOAD:api-ms-win-core-winrt-string-l1-1-0.dll",
     ]
 
+    if (is_multi_dll_chrome) {
+      ldflags += [
+        "/DELAYLOAD:dwrite.dll",
+        "/DELAYLOAD:iphlpapi.dll",
+        "/DELAYLOAD:oleaut32.dll",
+        "/DELAYLOAD:winmm.dll",
+        "/DELAYLOAD:ws2_32.dll",
+      ]
+    } else {
+      ldflags += [
+        "/DELAYLOAD:dxva2.dll",
+        "/DELAYLOAD:rstrtmgr.dll",
+      ]
+    }
+
     if (!is_component_build) {
       # This is a large module that can't do incremental linking in some
       # cases.
@@ -498,7 +507,10 @@
 
     if (is_multi_dll_chrome) {
       defines += [ "CHROME_MULTIPLE_DLL_BROWSER" ]
-      deps += [ "//content/public/app:browser" ]
+      deps += [
+        "//content/public/app:browser",
+        "//headless:headless_shell_browser_lib",
+      ]
       assert_no_deps = [
         # The browser DLL may not depend on blink or v8.
         "//third_party/blink/public:blink",
@@ -512,6 +524,7 @@
       deps += [
         ":child_dependencies",
         "//content/public/app:both",
+        "//headless:headless_shell_lib",
       ]
     }
 
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 88b6bd2..c357fcd 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -569,7 +569,7 @@
     "//chrome/browser/ntp_snippets/ntp_snippets_metrics.h",
     "//chrome/browser/profiles/profile_metrics.h",
     "//chrome/browser/translate/android/translate_utils.h",
-    "//chrome/browser/ui/android/bluetooth_scanning_prompt_android.h",
+    "//chrome/browser/ui/android/device_dialog/bluetooth_scanning_prompt_android.h",
     "//chrome/browser/ui/android/infobars/infobar_android.h",
   ]
 }
@@ -852,7 +852,6 @@
     "//third_party/android_deps:com_android_support_preference_v7_java",
     "//third_party/android_deps:com_android_support_recyclerview_v7_java",
     "//third_party/android_deps:com_android_support_support_annotations_java",
-    "//third_party/android_deps:com_google_ar_core_java",
     "//third_party/android_deps:com_google_protobuf_protobuf_lite_java",
     "//third_party/android_sdk:android_test_base_java",
     "//third_party/android_sdk:android_test_mock_java",
@@ -1039,6 +1038,7 @@
             "//third_party/gvr-android-sdk:gvr_common_java",
             ":chrome_test_util_java",
             "//components/module_installer/android:module_installer_java",
+            "//components/module_installer/android:module_installer_stub_java",
             "//components/module_installer/android:module_installer_test_java",
           ]
 
@@ -1756,6 +1756,8 @@
     android_manifest_dep = ":trichrome_library_android_manifest"
 
     if (trichrome_synchronized_proguard) {
+      shared_resources_whitelist_target = "//android_webview:system_webview_apk"
+      shared_resources_whitelist_locales = locales
       static_library_dependent_targets = [
         {
           name = "//android_webview:trichrome_webview_apk"
@@ -1777,6 +1779,8 @@
     android_manifest_dep = ":trichrome_library_android_manifest"
 
     if (trichrome_synchronized_proguard) {
+      shared_resources_whitelist_target = "//android_webview:system_webview_apk"
+      shared_resources_whitelist_locales = locales
       static_library_dependent_targets = [
         {
           name = "//android_webview:trichrome_webview_for_bundle_apk"
@@ -2424,8 +2428,6 @@
     "java/src/org/chromium/chrome/browser/AppHooks.java",
     "java/src/org/chromium/chrome/browser/ApplicationLifetime.java",
     "java/src/org/chromium/chrome/browser/BackgroundSyncLauncher.java",
-    "java/src/org/chromium/chrome/browser/BluetoothChooserDialog.java",
-    "java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java",
     "java/src/org/chromium/chrome/browser/ChromeBackupAgent.java",
     "java/src/org/chromium/chrome/browser/ChromeBackupWatcher.java",
     "java/src/org/chromium/chrome/browser/ChromeFeatureList.java",
@@ -2438,7 +2440,6 @@
     "java/src/org/chromium/chrome/browser/SearchGeolocationDisclosureTabHelper.java",
     "java/src/org/chromium/chrome/browser/ServiceTabLauncher.java",
     "java/src/org/chromium/chrome/browser/ShortcutHelper.java",
-    "java/src/org/chromium/chrome/browser/UsbChooserDialog.java",
     "java/src/org/chromium/chrome/browser/WarmupManager.java",
     "java/src/org/chromium/chrome/browser/WebContentsFactory.java",
     "java/src/org/chromium/chrome/browser/accessibility/FontSizePrefs.java",
@@ -2495,6 +2496,9 @@
     "java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java",
     "java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/ModuleMetrics.java",
     "java/src/org/chromium/chrome/browser/database/SQLiteCursor.java",
+    "java/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialog.java",
+    "java/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialog.java",
+    "java/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialog.java",
     "java/src/org/chromium/chrome/browser/document/DocumentWebContentsDelegate.java",
     "java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerServiceFactory.java",
     "java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerTabUtils.java",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 251b6cd..7047ad6 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -14,8 +14,6 @@
   "java/src/org/chromium/chrome/browser/ApplicationLifetime.java",
   "java/src/org/chromium/chrome/browser/AssistStatusHandler.java",
   "java/src/org/chromium/chrome/browser/BackgroundSyncLauncher.java",
-  "java/src/org/chromium/chrome/browser/BluetoothChooserDialog.java",
-  "java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java",
   "java/src/org/chromium/chrome/browser/BrowserRestartActivity.java",
   "java/src/org/chromium/chrome/browser/ChromeActionModeCallback.java",
   "java/src/org/chromium/chrome/browser/ChromeActivity.java",
@@ -40,15 +38,12 @@
   "java/src/org/chromium/chrome/browser/DelayedScreenLockIntentHandler.java",
   "java/src/org/chromium/chrome/browser/DevToolsServer.java",
   "java/src/org/chromium/chrome/browser/DeviceConditions.java",
-  "java/src/org/chromium/chrome/browser/DeviceItemAdapter.java",
-  "java/src/org/chromium/chrome/browser/DeviceItemRow.java",
   "java/src/org/chromium/chrome/browser/FileProviderHelper.java",
   "java/src/org/chromium/chrome/browser/GlobalDiscardableReferencePool.java",
   "java/src/org/chromium/chrome/browser/InsetObserverView.java",
   "java/src/org/chromium/chrome/browser/IntentHandler.java",
   "java/src/org/chromium/chrome/browser/IntentHeadersRecorder.java",
   "java/src/org/chromium/chrome/browser/IntentHelper.java",
-  "java/src/org/chromium/chrome/browser/ItemChooserDialog.java",
   "java/src/org/chromium/chrome/browser/KeyboardShortcuts.java",
   "java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java",
   "java/src/org/chromium/chrome/browser/LauncherShortcutActivity.java",
@@ -66,7 +61,6 @@
   "java/src/org/chromium/chrome/browser/SynchronousInitializationActivity.java",
   "java/src/org/chromium/chrome/browser/TabThemeColorProvider.java",
   "java/src/org/chromium/chrome/browser/ThemeColorProvider.java",
-  "java/src/org/chromium/chrome/browser/UsbChooserDialog.java",
   "java/src/org/chromium/chrome/browser/WarmupManager.java",
   "java/src/org/chromium/chrome/browser/WebContentsFactory.java",
   "java/src/org/chromium/chrome/browser/WindowDelegate.java",
@@ -447,6 +441,12 @@
   "java/src/org/chromium/chrome/browser/dependency_injection/ChromeCommonQualifiers.java",
   "java/src/org/chromium/chrome/browser/dependency_injection/ModuleFactoryOverrides.java",
   "java/src/org/chromium/chrome/browser/device/DeviceClassManager.java",
+  "java/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialog.java",
+  "java/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialog.java",
+  "java/src/org/chromium/chrome/browser/device_dialog/DeviceItemAdapter.java",
+  "java/src/org/chromium/chrome/browser/device_dialog/DeviceItemRow.java",
+  "java/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialog.java",
+  "java/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialog.java",
   "java/src/org/chromium/chrome/browser/directactions/ChromeDirectActionIds.java",
   "java/src/org/chromium/chrome/browser/directactions/MenuDirectActionHandler.java",
   "java/src/org/chromium/chrome/browser/directactions/DirectActionCoordinator.java",
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index d16003f..a92cc41 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -226,9 +226,6 @@
     if (_target_type == "android_app_bundle_module") {
       deps +=
           [ "//components/module_installer/android:module_installer_impl_java" ]
-    } else if (_target_type == "instrumentation_test_apk") {
-      deps +=
-          [ "//components/module_installer/android:module_installer_test_java" ]
     } else {
       deps +=
           [ "//components/module_installer/android:module_installer_stub_java" ]
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index d02d0795..5b9f9cc 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -9,8 +9,6 @@
   "javatests/src/org/chromium/chrome/browser/ActivityTabProviderTest.java",
   "javatests/src/org/chromium/chrome/browser/AudioTest.java",
   "javatests/src/org/chromium/chrome/browser/BackgroundSyncLauncherTest.java",
-  "javatests/src/org/chromium/chrome/browser/BluetoothChooserDialogTest.java",
-  "javatests/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialogTest.java",
   "javatests/src/org/chromium/chrome/browser/ChromeActivityTest.java",
   "javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java",
   "javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java",
@@ -21,7 +19,6 @@
   "javatests/src/org/chromium/chrome/browser/HTTPSTabsOpenedFromExternalAppTest.java",
   "javatests/src/org/chromium/chrome/browser/InstalledAppTest.java",
   "javatests/src/org/chromium/chrome/browser/IntentHandlerTest.java",
-  "javatests/src/org/chromium/chrome/browser/ItemChooserDialogTest.java",
   "javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java",
   "javatests/src/org/chromium/chrome/browser/MainActivityWithURLTest.java",
   "javatests/src/org/chromium/chrome/browser/MockSafeBrowsingApiHandler.java",
@@ -47,7 +44,6 @@
   "javatests/src/org/chromium/chrome/browser/TabsOpenedFromExternalAppTest.java",
   "javatests/src/org/chromium/chrome/browser/TabsTest.java",
   "javatests/src/org/chromium/chrome/browser/UrlSchemeTest.java",
-  "javatests/src/org/chromium/chrome/browser/UsbChooserDialogTest.java",
   "javatests/src/org/chromium/chrome/browser/VideoFullscreenOrientationLockChromeTest.java",
   "javatests/src/org/chromium/chrome/browser/WarmupManagerTest.java",
   "javatests/src/org/chromium/chrome/browser/accessibility/FontSizePrefsTest.java",
@@ -79,6 +75,10 @@
   "javatests/src/org/chromium/chrome/browser/compositor/layouts/MockLayoutHost.java",
   "javatests/src/org/chromium/chrome/browser/compositor/layouts/MockResourcesForLayout.java",
   "javatests/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripTest.java",
+  "javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialogTest.java",
+  "javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialogTest.java",
+  "javatests/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialogTest.java",
+  "javatests/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialogTest.java",
   "javatests/src/org/chromium/chrome/browser/directactions/CloseTabDirectActionHandlerTest.java",
   "javatests/src/org/chromium/chrome/browser/directactions/DirectActionAvailabilityTest.java",
   "javatests/src/org/chromium/chrome/browser/directactions/DirectActionTestRule.java",
diff --git a/chrome/android/features/autofill_assistant/BUILD.gn b/chrome/android/features/autofill_assistant/BUILD.gn
index a3f2d62..919d5a7 100644
--- a/chrome/android/features/autofill_assistant/BUILD.gn
+++ b/chrome/android/features/autofill_assistant/BUILD.gn
@@ -72,7 +72,7 @@
     "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantTagsForTesting.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantClient.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactoryImpl.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetUtils.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/EditDistance.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/FeedbackContext.java",
@@ -162,6 +162,7 @@
     "javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPaymentRequestUiTest.java",
     "javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java",
     "javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java",
+    "javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java",
     "javatests/src/org/chromium/chrome/browser/autofill_assistant/EditDistanceTest.java",
   ]
 
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
index ac3e29a..3237350 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
@@ -257,7 +257,8 @@
 
     /** Request showing the Assistant bottom bar view and expand the sheet. */
     public void showAndExpand() {
-        BottomSheetUtils.showContentAndExpand(mBottomSheetController, mContent);
+        BottomSheetUtils.showContentAndExpand(
+                mBottomSheetController, mContent, /* animate= */ true);
     }
 
     /** Hide the Assistant bottom bar view. */
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java
index f9d27be..776880d 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.autofill_assistant;
 
 import android.content.Context;
+import android.support.annotation.Nullable;
 import android.text.SpannableString;
 import android.text.method.LinkMovementMethod;
 import android.view.LayoutInflater;
@@ -13,8 +14,15 @@
 import android.widget.TextView;
 
 import org.chromium.base.Callback;
+import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.autofill_assistant.R;
+import org.chromium.chrome.browser.autofill_assistant.metrics.OnBoarding;
+import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayCoordinator;
+import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayModel;
+import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayState;
 import org.chromium.chrome.browser.customtabs.CustomTabActivity;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
 import org.chromium.ui.text.NoUnderlineClickableSpan;
 import org.chromium.ui.text.SpanApplier;
 
@@ -27,24 +35,112 @@
 class AssistantOnboardingCoordinator {
     private static final String SMALL_ONBOARDING_EXPERIMENT_ID = "4257013";
 
+    private final String mExperimentIds;
+    private final Context mContext;
+    private final BottomSheetController mController;
+    @Nullable
+    private final Tab mTab;
+
+    @Nullable
+    private AssistantOverlayCoordinator mOverlayCoordinator;
+
+    @Nullable
+    private AssistantBottomSheetContent mContent;
+    private boolean mAnimate = true;
+
+    AssistantOnboardingCoordinator(String experimentIds, Context context,
+            BottomSheetController controller, @Nullable Tab tab) {
+        mExperimentIds = experimentIds;
+        mContext = context;
+        mController = controller;
+        mTab = tab;
+    }
+
     /**
-     * Set the content of {@code bottomSheetContent} to be the Autofill Assistant onboarding. {@code
-     * callback} will be called with true or false when the user accepts or cancels the onboarding
-     * (respectively).
+     * Shows onboarding and provides the result to the given callback.
+     *
+     * <p>The {@code callback} will be called with true or false when the user accepts or cancels
+     * the onboarding (respectively).
+     *
+     * <p>Note that the bottom sheet will be hidden after the callback returns. Call, from the
+     * callback, {@link #hide} to hide it earlier or {@link #transferControls} to take ownership of
+     * it and possibly keep it past the end of the callback.
      */
-    static void setOnboardingContent(String experimentIds, Context context,
-            AssistantBottomSheetContent bottomSheetContent, Callback<Boolean> callback) {
-        ScrollView initView = (ScrollView) LayoutInflater.from(context).inflate(
+    void show(Callback<Boolean> callback) {
+        AutofillAssistantMetrics.recordOnBoarding(OnBoarding.OB_SHOWN);
+
+        if (mTab != null) {
+            // If there's a tab, cover it with an overlay.
+            AssistantOverlayModel overlayModel = new AssistantOverlayModel();
+            mOverlayCoordinator = new AssistantOverlayCoordinator(mTab.getActivity(), overlayModel);
+            overlayModel.set(AssistantOverlayModel.STATE, AssistantOverlayState.FULL);
+        }
+        mContent = new AssistantBottomSheetContent(mContext);
+        initContent(callback);
+        BottomSheetUtils.showContentAndExpand(mController, mContent, mAnimate);
+    }
+
+    /**
+     * Transfers ownership of the controls to the caller, returns the overlay coordinator, if one
+     * was created.
+     *
+     * <p>This call is only useful when called from inside a callback provided to {@link #show}, as
+     * before that there are no controls and after that the coordinator automatically hides them.
+     * This call allows callbacks to reuse the controls setup for onboarding and provide a smooth
+     * transition.
+     */
+    @Nullable
+    AssistantOverlayCoordinator transferControls() {
+        assert isInProgress();
+
+        AssistantOverlayCoordinator coordinator = mOverlayCoordinator;
+        mOverlayCoordinator = null;
+        mContent = null;
+        return coordinator;
+    }
+
+    /** Hides the UI, if one is shown. */
+    void hide() {
+        if (mOverlayCoordinator != null) {
+            mOverlayCoordinator.destroy();
+            mOverlayCoordinator = null;
+        }
+
+        if (mContent != null) {
+            mController.hideContent(mContent, /* animate= */ mAnimate);
+            mContent = null;
+        }
+    }
+
+    /**
+     * Returns {@code true} between the time {@link #show} is called and the time
+     * the callback has returned.
+     */
+    boolean isInProgress() {
+        return mContent != null;
+    }
+
+    /** Don't animate the bottom sheet expansion. */
+    @VisibleForTesting
+    void disableAnimationForTesting() {
+        mAnimate = false;
+    }
+
+    /**
+     * Set the content of the bottom sheet to be the Autofill Assistant onboarding.
+     */
+    private void initContent(Callback<Boolean> callback) {
+        ScrollView initView = (ScrollView) LayoutInflater.from(mContext).inflate(
                 R.layout.autofill_assistant_onboarding, /* root= */ null);
 
         TextView termsTextView = initView.findViewById(R.id.google_terms_message);
-        String termsString = context.getApplicationContext().getString(
+        String termsString = mContext.getApplicationContext().getString(
                 R.string.autofill_assistant_google_terms_description);
 
-        NoUnderlineClickableSpan termsSpan = new NoUnderlineClickableSpan(context.getResources(),
+        NoUnderlineClickableSpan termsSpan = new NoUnderlineClickableSpan(mContext.getResources(),
                 (widget)
-                        -> CustomTabActivity.showInfoPage(context.getApplicationContext(),
-                                context.getApplicationContext().getString(
+                        -> CustomTabActivity.showInfoPage(mContext.getApplicationContext(),
+                                mContext.getApplicationContext().getString(
                                         R.string.autofill_assistant_google_terms_url)));
         SpannableString spannableMessage = SpanApplier.applySpans(
                 termsString, new SpanApplier.SpanInfo("<link>", "</link>", termsSpan));
@@ -59,24 +155,27 @@
         initView.findViewById(R.id.button_init_not_ok)
                 .setOnClickListener(unusedView -> onClicked(false, callback));
         initView.setContentDescription(
-                context.getString(R.string.autofill_assistant_first_run_accessibility));
+                mContext.getString(R.string.autofill_assistant_first_run_accessibility));
 
         // Hide views that should not be displayed when showing the small onboarding.
-        if (Arrays.asList(experimentIds.split(",")).contains(SMALL_ONBOARDING_EXPERIMENT_ID)) {
+        if (Arrays.asList(mExperimentIds.split(",")).contains(SMALL_ONBOARDING_EXPERIMENT_ID)) {
             hide(initView, R.id.onboarding_image);
             hide(initView, R.id.onboarding_subtitle);
             hide(initView, R.id.onboarding_separator);
         }
 
-        bottomSheetContent.setContent(initView, initView);
+        mContent.setContent(initView, initView);
     }
 
     private static void hide(View root, int resId) {
         root.findViewById(resId).setVisibility(View.GONE);
     }
 
-    private static void onClicked(boolean accept, Callback<Boolean> callback) {
+    private void onClicked(boolean accept, Callback<Boolean> callback) {
         AutofillAssistantPreferencesUtil.setInitialPreferences(accept);
+        AutofillAssistantMetrics.recordOnBoarding(
+                accept ? OnBoarding.OB_ACCEPTED : OnBoarding.OB_CANCELLED);
         callback.onResult(accept);
+        hide();
     }
 }
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactoryImpl.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactoryImpl.java
deleted file mode 100644
index 2eba277..0000000
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactoryImpl.java
+++ /dev/null
@@ -1,89 +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.
-
-package org.chromium.chrome.browser.autofill_assistant;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import org.chromium.base.annotations.UsedByReflection;
-import org.chromium.chrome.browser.ChromeActivity;
-import org.chromium.chrome.browser.autofill_assistant.metrics.DropOutReason;
-import org.chromium.chrome.browser.autofill_assistant.metrics.OnBoarding;
-import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayCoordinator;
-import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayModel;
-import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayState;
-import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
-import org.chromium.content_public.browser.WebContents;
-
-import java.util.Map;
-
-/**
- * Factory implementation to create AutofillAssistantClient as
- * as AutofillAssistantModuleEntry to serve as interface between
- * base module and assistant DFM.
- */
-@UsedByReflection("AutofillAssistantModuleEntryProvider.java")
-public class AutofillAssistantModuleEntryFactoryImpl
-        implements AutofillAssistantModuleEntryFactory {
-    @Override
-    public AutofillAssistantModuleEntry createEntry(
-            @NonNull ChromeActivity activity, @NonNull WebContents webContents) {
-        return new AutofillAssistantModuleEntryImpl(activity, webContents);
-    }
-
-    private static class AutofillAssistantModuleEntryImpl implements AutofillAssistantModuleEntry {
-        private final ChromeActivity mActivity;
-        private final WebContents mWebContents;
-
-        private AutofillAssistantModuleEntryImpl(ChromeActivity activity, WebContents webContents) {
-            mActivity = activity;
-            mWebContents = webContents;
-        }
-
-        @Override
-        public void start(boolean skipOnboarding, String initialUrl, Map<String, String> parameters,
-                String experimentIds, Bundle intentExtras) {
-            if (skipOnboarding) {
-                AutofillAssistantMetrics.recordOnBoarding(OnBoarding.OB_NOT_SHOWN);
-
-                start(initialUrl, parameters, experimentIds, intentExtras, null);
-            } else {
-                AutofillAssistantMetrics.recordOnBoarding(OnBoarding.OB_SHOWN);
-
-                BottomSheetController controller = mActivity.getBottomSheetController();
-                AssistantBottomSheetContent content = new AssistantBottomSheetContent(mActivity);
-                AssistantOverlayModel overlayModel = new AssistantOverlayModel();
-                AssistantOverlayCoordinator overlayCoordinator =
-                        new AssistantOverlayCoordinator(mActivity, overlayModel);
-                overlayModel.set(AssistantOverlayModel.STATE, AssistantOverlayState.FULL);
-                AssistantOnboardingCoordinator.setOnboardingContent(
-                        experimentIds, mActivity, content, accepted -> {
-                            if (accepted) {
-                                AutofillAssistantMetrics.recordOnBoarding(OnBoarding.OB_ACCEPTED);
-
-                                // We transfer the ownership of the overlay to #start() and the
-                                // bottom sheet content will be replaced.
-                                start(initialUrl, parameters, experimentIds, intentExtras,
-                                        overlayCoordinator);
-                            } else {
-                                AutofillAssistantMetrics.recordOnBoarding(OnBoarding.OB_CANCELLED);
-                                overlayCoordinator.destroy();
-                                controller.hideContent(content, /* animate= */ true);
-                                AutofillAssistantMetrics.recordDropOut(DropOutReason.DECLINED);
-                            }
-                        });
-
-                BottomSheetUtils.showContentAndExpand(controller, content);
-            }
-        }
-
-        private void start(String initialUrl, Map<String, String> parameters, String experimentIds,
-                Bundle intentExtras, @Nullable AssistantOverlayCoordinator overlayCoordinator) {
-            AutofillAssistantClient client = AutofillAssistantClient.fromWebContents(mWebContents);
-            client.start(initialUrl, parameters, experimentIds, intentExtras, overlayCoordinator);
-        }
-    }
-}
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java
new file mode 100644
index 0000000..f415368
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryImpl.java
@@ -0,0 +1,46 @@
+// 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.chrome.browser.autofill_assistant;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+import org.chromium.base.annotations.UsedByReflection;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.autofill_assistant.metrics.OnBoarding;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.content_public.browser.WebContents;
+
+import java.util.Map;
+
+/**
+ * Implementation of {@link AutofillAssistantModuleEntry}. This is the entry point into the
+ * assistant DFM.
+ */
+@UsedByReflection("AutofillAssistantModuleEntryProvider.java")
+public class AutofillAssistantModuleEntryImpl implements AutofillAssistantModuleEntry {
+    @Override
+    public void start(@NonNull Tab tab, @NonNull WebContents webContents, boolean skipOnboarding,
+            String initialUrl, Map<String, String> parameters, String experimentIds,
+            Bundle intentExtras) {
+        if (skipOnboarding) {
+            AutofillAssistantMetrics.recordOnBoarding(OnBoarding.OB_NOT_SHOWN);
+            AutofillAssistantClient.fromWebContents(tab.getWebContents())
+                    .start(initialUrl, parameters, experimentIds, intentExtras, null);
+            return;
+        }
+
+        ChromeActivity activity = tab.getActivity();
+        AssistantOnboardingCoordinator onboardingCoordinator = new AssistantOnboardingCoordinator(
+                experimentIds, activity, activity.getBottomSheetController(), tab);
+        onboardingCoordinator.show(accepted -> {
+            if (!accepted) return;
+
+            AutofillAssistantClient.fromWebContents(tab.getWebContents())
+                    .start(initialUrl, parameters, experimentIds, intentExtras,
+                            onboardingCoordinator.transferControls());
+        });
+    }
+}
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetUtils.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetUtils.java
index 3c2d6dd..695c588 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetUtils.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/BottomSheetUtils.java
@@ -13,8 +13,8 @@
 
 class BottomSheetUtils {
     /** Request {@code controller} to show {@code content} and expand the sheet when it is shown. */
-    static void showContentAndExpand(
-            BottomSheetController controller, AssistantBottomSheetContent content) {
+    static void showContentAndExpand(BottomSheetController controller,
+            AssistantBottomSheetContent content, boolean animate) {
         // Add an observer that makes sure the bottom sheet content is always shown, even in the
         // peek state.
         BottomSheet bottomSheet = controller.getBottomSheet();
@@ -62,7 +62,7 @@
         });
 
         // Show the content.
-        if (controller.requestShowContent(content, /* animate= */ true)) {
+        if (controller.requestShowContent(content, animate)) {
             controller.expandSheet();
         } else {
             // If the content is not directly shown, add an observer that will expand the sheet when
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java
index 3165381..f593c37 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java
@@ -137,7 +137,7 @@
         view.mProfileIconMenu.setOnMenuItemClickListener(item -> {
             int itemId = item.getItemId();
             if (itemId == R.id.settings) {
-                PreferencesLauncher.launchSettingsPage(
+                PreferencesLauncher.launchSettingsPageCompat(
                         view.mHeader.getContext(), AutofillAssistantPreferences.class);
                 return true;
             } else if (itemId == R.id.send_feedback) {
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java
new file mode 100644
index 0000000..330d373
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinatorTest.java
@@ -0,0 +1,153 @@
+// 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.chrome.browser.autofill_assistant;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.assertThat;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+
+import android.support.annotation.IdRes;
+import android.support.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import org.chromium.base.Callback;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.chrome.autofill_assistant.R;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayCoordinator;
+import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayModel;
+import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayState;
+import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests {@link AssistantOnboardingCoordinator}
+ */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)
+public class AssistantOnboardingCoordinatorTest {
+    @Rule
+    public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();
+
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock
+    Callback<Boolean> mCallback;
+
+    private ChromeActivity mActivity;
+    private BottomSheetController mBottomSheetController;
+    private Tab mTab;
+
+    @Before
+    public void setUp() throws Exception {
+        AutofillAssistantUiTestUtil.startOnBlankPage(mCustomTabActivityTestRule);
+        mActivity = mCustomTabActivityTestRule.getActivity();
+        mBottomSheetController = ThreadUtils.runOnUiThreadBlocking(
+                () -> AutofillAssistantUiTestUtil.createBottomSheetController(mActivity));
+        mTab = mActivity.getTabModelSelector().getCurrentTab();
+    }
+
+    private AssistantOnboardingCoordinator createCoordinator(Tab tab) {
+        AssistantOnboardingCoordinator coordinator =
+                new AssistantOnboardingCoordinator("", mActivity, mBottomSheetController, mTab);
+        coordinator.disableAnimationForTesting();
+        return coordinator;
+    }
+
+    @Test
+    @MediumTest
+    public void testAcceptOnboarding() throws Exception {
+        testOnboarding(R.id.button_init_ok, true);
+    }
+
+    @Test
+    @MediumTest
+    public void testRejectOnboarding() throws Exception {
+        testOnboarding(R.id.button_init_not_ok, false);
+    }
+
+    private void testOnboarding(@IdRes int buttonToClick, boolean expectAccept) throws Exception {
+        AutofillAssistantPreferencesUtil.setInitialPreferences(!expectAccept);
+
+        AssistantOnboardingCoordinator coordinator = createCoordinator(mTab);
+
+        ThreadUtils.runOnUiThreadBlocking(() -> coordinator.show(mCallback));
+
+        assertTrue(ThreadUtils.runOnUiThreadBlocking(coordinator::isInProgress));
+        onView(is(mActivity.getScrim())).check(matches(isDisplayed()));
+        onView(withId(buttonToClick)).perform(click());
+
+        verify(mCallback).onResult(expectAccept);
+
+        assertFalse(ThreadUtils.runOnUiThreadBlocking(coordinator::isInProgress));
+        assertEquals(expectAccept, AutofillAssistantPreferencesUtil.isAutofillOnboardingAccepted());
+    }
+
+    @Test
+    @MediumTest
+    public void testOnboardingWithNoTabs() throws Exception {
+        AssistantOnboardingCoordinator coordinator = createCoordinator(/* tab= */ null);
+
+        ThreadUtils.runOnUiThreadBlocking(() -> coordinator.show(mCallback));
+
+        onView(withId(R.id.button_init_ok)).perform(click());
+
+        verify(mCallback).onResult(true);
+    }
+
+    @Test
+    @MediumTest
+    public void testTransfertControls() throws Exception {
+        AssistantOnboardingCoordinator coordinator = createCoordinator(mTab);
+
+        List<AssistantOverlayCoordinator> capturedOverlays =
+                Collections.synchronizedList(new ArrayList<>());
+        ThreadUtils.runOnUiThreadBlocking(() -> coordinator.show((accepted) -> {
+            capturedOverlays.add(coordinator.transferControls());
+        }));
+
+        onView(withId(R.id.button_init_ok)).perform(click());
+        assertFalse(ThreadUtils.runOnUiThreadBlocking(coordinator::isInProgress));
+
+        // An overlay was captured, and it is still shown.
+        onView(is(mActivity.getScrim())).check(matches(isDisplayed()));
+        assertEquals(1, capturedOverlays.size());
+        AssistantOverlayCoordinator overlay = capturedOverlays.get(0);
+        assertNotNull(overlay);
+        assertEquals(
+                AssistantOverlayState.FULL, overlay.getModel().get(AssistantOverlayModel.STATE));
+
+        // The bottom sheet content is still the assistant one.
+        assertThat(mBottomSheetController.getBottomSheet().getCurrentSheetContent(),
+                instanceOf(AssistantBottomSheetContent.class));
+    }
+}
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
index 540c4b6..fd52f00 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
@@ -12,15 +12,11 @@
 
 import static org.hamcrest.Matchers.not;
 import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.verify;
 
-import android.content.Context;
 import android.content.Intent;
-import android.support.annotation.IdRes;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
@@ -55,8 +51,6 @@
 import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
 import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
 import org.chromium.chrome.browser.firstrun.FirstRunStatus;
-import org.chromium.chrome.browser.snackbar.BottomContainer;
-import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -111,53 +105,10 @@
         return mCustomTabActivityTestRule.getActivity();
     }
 
-    // Copied from {@link ChromeActivity#initializeBottomSheet}.
     protected BottomSheetController initializeBottomSheet() {
-        CustomTabActivity activity = getActivity();
-        ViewGroup coordinator = activity.findViewById(R.id.coordinator);
-        LayoutInflater.from(activity).inflate(R.layout.bottom_sheet, coordinator);
-        BottomSheet bottomSheet = coordinator.findViewById(R.id.bottom_sheet);
-        bottomSheet.init(coordinator, activity);
-
-        ((BottomContainer) activity.findViewById(R.id.bottom_container))
-                .setBottomSheet(bottomSheet);
-
-        return new BottomSheetController(activity, activity.getLifecycleDispatcher(),
-                activity.getActivityTabProvider(), activity.getScrim(), bottomSheet,
-                activity.getCompositorViewHolder().getLayoutManager().getOverlayPanelManager(),
-                /* suppressSheetForContextualSearch= */ false);
+        return AutofillAssistantUiTestUtil.createBottomSheetController(getActivity());
     }
 
-    @Test
-    @MediumTest
-    public void testAcceptOnboarding() throws Exception {
-        testOnboarding(true, R.id.button_init_ok);
-    }
-
-    @Test
-    @MediumTest
-    public void testDeclineOnboarding() throws Exception {
-        testOnboarding(false, R.id.button_init_not_ok);
-    }
-
-    private void testOnboarding(boolean expectedAccepted, @IdRes int buttonToClick)
-            throws Exception {
-        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
-        Context context = getActivity();
-        AssistantBottomSheetContent bottomSheetContent = new AssistantBottomSheetContent(context);
-
-        AssistantOnboardingCoordinator.setOnboardingContent(
-                /* experimentIds= */ "", context, bottomSheetContent, accepted -> {
-                    Assert.assertEquals(expectedAccepted, accepted);
-                    mRunnableMock.run();
-                });
-
-        View button = bottomSheetContent.getContentView().findViewById(buttonToClick);
-        Assert.assertNotNull(button);
-
-        TestThreadUtils.runOnUiThreadBlocking(button::performClick);
-        TestThreadUtils.runOnUiThreadBlocking(() -> verify(mRunnableMock).run());
-    }
 
     // TODO(crbug.com/806868): Add more UI details test and check, like payment request UI,
     // highlight chips and so on.
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java
index 45d18ee..7ec9b81 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTestUtil.java
@@ -9,6 +9,7 @@
 import android.support.design.widget.CoordinatorLayout;
 import android.support.test.InstrumentationRegistry;
 import android.view.Gravity;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
@@ -18,11 +19,16 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.ThreadUtils;
+import org.chromium.chrome.autofill_assistant.R;
+import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.customtabs.CustomTabActivity;
 import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
 import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
 import org.chromium.chrome.browser.image_fetcher.ImageFetcher;
 import org.chromium.chrome.browser.image_fetcher.ImageFetcherConfig;
+import org.chromium.chrome.browser.snackbar.BottomContainer;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
 
 import jp.tomorrowkey.android.gifplayer.BaseGifImage;
 
@@ -79,6 +85,29 @@
     }
 
     /**
+     * Creates a {@link BottomSheetController} for the activity, suitable for testing.
+     *
+     * <p>The returned controller is different from the one returned by {@link
+     * ChromeActivity#getBottomSheetController}.
+     */
+    static BottomSheetController createBottomSheetController(ChromeActivity activity) {
+        // Copied from {@link ChromeActivity#initializeBottomSheet}.
+
+        ViewGroup coordinator = activity.findViewById(R.id.coordinator);
+        LayoutInflater.from(activity).inflate(R.layout.bottom_sheet, coordinator);
+        BottomSheet bottomSheet = coordinator.findViewById(R.id.bottom_sheet);
+        bottomSheet.init(coordinator, activity);
+
+        ((BottomContainer) activity.findViewById(R.id.bottom_container))
+                .setBottomSheet(bottomSheet);
+
+        return new BottomSheetController(activity, activity.getLifecycleDispatcher(),
+                activity.getActivityTabProvider(), activity.getScrim(), bottomSheet,
+                activity.getCompositorViewHolder().getLayoutManager().getOverlayPanelManager(),
+                /* suppressSheetForContextualSearch= */ false);
+    }
+
+    /**
      * Attaches the specified view to the Chrome coordinator. Must be called from the UI thread.
      */
     public static void attachToCoordinator(CustomTabActivity activity, View view) {
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
index 8a92e67..8b0df5d 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
@@ -11,12 +11,15 @@
 import android.support.annotation.Nullable;
 
 import org.chromium.base.BuildInfo;
+import org.chromium.base.Callback;
+import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeActivity.ActivityType;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.autofill_assistant.metrics.DropOutReason;
 import org.chromium.chrome.browser.directactions.DirectActionHandler;
 import org.chromium.chrome.browser.metrics.UmaSessionStats;
+import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.chrome.browser.widget.ScrimView;
@@ -104,18 +107,20 @@
 
         // Have an "attempted starts" baseline for the drop out histogram.
         AutofillAssistantMetrics.recordDropOut(DropOutReason.AA_START);
-        AutofillAssistantModuleEntryProvider.getModuleEntry(activity, (moduleEntry) -> {
-            if (moduleEntry == null) {
-                AutofillAssistantMetrics.recordDropOut(DropOutReason.DFM_INSTALL_FAILED);
-                return;
-            }
+        waitForTabWithWebContents(activity, tab -> {
+            AutofillAssistantModuleEntryProvider.getModuleEntry(activity, tab, (moduleEntry) -> {
+                if (moduleEntry == null) {
+                    AutofillAssistantMetrics.recordDropOut(DropOutReason.DFM_INSTALL_FAILED);
+                    return;
+                }
 
-            Bundle bundleExtras = activity.getInitialIntent().getExtras();
-            Map<String, String> parameters = extractParameters(bundleExtras);
-            parameters.remove(PARAMETER_ENABLED);
-            String initialUrl = activity.getInitialIntent().getDataString();
-            moduleEntry.start(canStartWithoutOnboarding, initialUrl, parameters, experimentIds,
-                    activity.getInitialIntent().getExtras());
+                Bundle bundleExtras = activity.getInitialIntent().getExtras();
+                Map<String, String> parameters = extractParameters(bundleExtras);
+                parameters.remove(PARAMETER_ENABLED);
+                String initialUrl = activity.getInitialIntent().getDataString();
+                moduleEntry.start(tab, tab.getWebContents(), canStartWithoutOnboarding, initialUrl,
+                        parameters, experimentIds, activity.getInitialIntent().getExtras());
+            });
         });
     }
 
@@ -227,4 +232,25 @@
         }
         return false;
     }
+
+    /** Provides the callback with a tab that has a web contents, waits if necessary. */
+    private static void waitForTabWithWebContents(ChromeActivity activity, Callback<Tab> callback) {
+        if (activity.getActivityTab() != null
+                && activity.getActivityTab().getWebContents() != null) {
+            callback.onResult(activity.getActivityTab());
+            return;
+        }
+
+        // The tab is not yet available. We need to register as listener and wait for it.
+        activity.getActivityTabProvider().addObserverAndTrigger(
+                new ActivityTabProvider.HintlessActivityTabObserver() {
+                    @Override
+                    public void onActivityTabChanged(Tab tab) {
+                        if (tab == null) return;
+                        activity.getActivityTabProvider().removeObserver(this);
+                        assert tab.getWebContents() != null;
+                        callback.onResult(tab);
+                    }
+                });
+    }
 }
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java
index 44b802e..590dc7b 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java
@@ -5,19 +5,28 @@
 package org.chromium.chrome.browser.autofill_assistant;
 
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.components.module_installer.ModuleInterface;
+import org.chromium.content_public.browser.WebContents;
 
 import java.util.Map;
 
 /**
- * Interface for base module to start the autofill assistant
- * experience in dynamic feature module.
+ * Interface between base module and assistant DFM.
  */
+@ModuleInterface(module = "autofill_assistant",
+        impl = "org.chromium.chrome.browser.autofill_assistant.AutofillAssistantModuleEntryImpl")
 interface AutofillAssistantModuleEntry {
     /**
-     * Launches Autofill Assistant on the current web contents, expecting autostart. If {@code
-     * skipOnboarding} is false, the onboarding will first be shown and the Autofill Assistant will
-     * start only if the user accepts to proceed.
+     * Starts Autofill Assistant on the current tab of the given chrome activity.
+     *
+     * <p>When started this way, Autofill Assistant appears immediately in the bottom sheet, expects
+     * a single autostartable script for the tab's current URL, runs that script until the end and
+     * disappears.
      */
-    void start(boolean skipOnboarding, String initialUrl, Map<String, String> parameters,
-            String experimentIds, Bundle intentExtras);
-}
\ No newline at end of file
+    void start(@NonNull Tab tab, @NonNull WebContents webContents, boolean skipOnboarding,
+            String initialUrl, Map<String, String> parameters, String experimentIds,
+            Bundle intentExtras);
+}
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactory.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactory.java
deleted file mode 100644
index 214ce76..0000000
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactory.java
+++ /dev/null
@@ -1,23 +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.
-
-package org.chromium.chrome.browser.autofill_assistant;
-
-import android.support.annotation.NonNull;
-
-import org.chromium.chrome.browser.ChromeActivity;
-import org.chromium.components.module_installer.ModuleInterface;
-import org.chromium.content_public.browser.WebContents;
-
-/**
- * Interface to create AutofillAssistantModuleEntry as inferface
- * between base module and assistant DFM.
- */
-@ModuleInterface(module = "autofill_assistant",
-        impl = "org.chromium.chrome.browser.autofill_assistant."
-                + "AutofillAssistantModuleEntryFactoryImpl")
-interface AutofillAssistantModuleEntryFactory {
-    AutofillAssistantModuleEntry createEntry(
-            @NonNull ChromeActivity activity, @NonNull WebContents webContents);
-}
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryProvider.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryProvider.java
index 517615c..991c3f0 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryProvider.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryProvider.java
@@ -4,13 +4,14 @@
 
 package org.chromium.chrome.browser.autofill_assistant;
 
+import android.content.Context;
+import android.support.annotation.Nullable;
+
 import org.chromium.base.BundleUtils;
 import org.chromium.base.Callback;
 import org.chromium.base.Log;
 import org.chromium.base.SysUtils;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ActivityTabProvider;
-import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.modules.ModuleInstallUi;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.module_installer.ModuleInstaller;
@@ -22,21 +23,26 @@
 public class AutofillAssistantModuleEntryProvider {
     private static final String TAG = "AutofillAssistant";
 
-    /**
-     * Returns AutofillAssistantModuleEntry by using it as argument to the
-     * passed in callback, or null if DFM loading fails.
-     */
+    /* Returns the AA module entry, if it is already installed. */
+    @Nullable
+    /* package */ static AutofillAssistantModuleEntry getModuleEntryIfInstalled(Context context) {
+        // Required to access resources in DFM using this activity as context.
+        ModuleInstaller.getInstance().initActivity(context);
+        if (AutofillAssistantModule.isInstalled()) {
+            return AutofillAssistantModule.getImpl();
+        }
+        return null;
+    }
+
+    /** Gets the AA module entry, installing it if necessary. */
     /* package */ static void getModuleEntry(
-            ChromeActivity activity, Callback<AutofillAssistantModuleEntry> callback) {
-        getTab(activity, tab -> {
-            // Required to access resources in DFM using this activity as context.
-            ModuleInstaller.initActivity(activity);
-            if (AutofillAssistantModule.isInstalled()) {
-                callback.onResult(createEntry(tab));
-                return;
-            }
-            loadDynamicModuleWithUi(activity, tab, callback);
-        });
+            Context context, Tab tab, Callback<AutofillAssistantModuleEntry> callback) {
+        AutofillAssistantModuleEntry entry = getModuleEntryIfInstalled(context);
+        if (entry != null) {
+            callback.onResult(entry);
+            return;
+        }
+        loadDynamicModuleWithUi(context, tab, callback);
     }
 
     /**
@@ -70,13 +76,8 @@
         AutofillAssistantModule.installDeferred();
     }
 
-    private static AutofillAssistantModuleEntry createEntry(Tab tab) {
-        AutofillAssistantModuleEntryFactory factory = AutofillAssistantModule.getImpl();
-        return factory.createEntry(tab.getActivity(), tab.getWebContents());
-    }
-
     private static void loadDynamicModuleWithUi(
-            ChromeActivity activity, Tab tab, Callback<AutofillAssistantModuleEntry> callback) {
+            Context activity, Tab tab, Callback<AutofillAssistantModuleEntry> callback) {
         ModuleInstallUi ui = new ModuleInstallUi(tab, R.string.autofill_assistant_module_title,
                 new ModuleInstallUi.FailureUiListener() {
                     @Override
@@ -91,37 +92,17 @@
                 });
         // Shows toast informing user about install start.
         ui.showInstallStartUi();
-        ModuleInstaller.install("autofill_assistant", (success) -> {
+        ModuleInstaller.getInstance().install("autofill_assistant", (success) -> {
             if (success) {
                 // Clean install of chrome will have issues here without initializing
                 // after installation of DFM.
-                ModuleInstaller.initActivity(activity);
+                ModuleInstaller.getInstance().initActivity(activity);
                 // Don't show success UI from DFM, transition to autobot UI directly.
-                callback.onResult(createEntry(tab));
+                callback.onResult(AutofillAssistantModule.getImpl());
                 return;
             }
             // Show inforbar to ask user if they want to retry or cancel.
             ui.showInstallFailureUi();
         });
     }
-
-    private static void getTab(ChromeActivity activity, Callback<Tab> callback) {
-        if (activity.getActivityTab() != null
-                && activity.getActivityTab().getWebContents() != null) {
-            callback.onResult(activity.getActivityTab());
-            return;
-        }
-
-        // The tab is not yet available. We need to register as listener and wait for it.
-        activity.getActivityTabProvider().addObserverAndTrigger(
-                new ActivityTabProvider.HintlessActivityTabObserver() {
-                    @Override
-                    public void onActivityTabChanged(Tab tab) {
-                        if (tab == null) return;
-                        activity.getActivityTabProvider().removeObserver(this);
-                        assert tab.getWebContents() != null;
-                        callback.onResult(tab);
-                    }
-                });
-    }
 }
diff --git a/chrome/android/features/autofill_assistant/public/java_sources.gni b/chrome/android/features/autofill_assistant/public/java_sources.gni
index 1d79e4d..357f8ea 100644
--- a/chrome/android/features/autofill_assistant/public/java_sources.gni
+++ b/chrome/android/features/autofill_assistant/public/java_sources.gni
@@ -6,7 +6,6 @@
   "//chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java",
   "//chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantMetrics.java",
   "//chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntry.java",
-  "//chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryFactory.java",
   "//chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantModuleEntryProvider.java",
   "//chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPreferencesUtil.java",
 ]
diff --git a/chrome/android/features/keyboard_accessory/internal/java/res/layout/keyboard_accessory_sheet_tab_address_info.xml b/chrome/android/features/keyboard_accessory/internal/java/res/layout/keyboard_accessory_sheet_tab_address_info.xml
index a06df67..5ea4e1a 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/res/layout/keyboard_accessory_sheet_tab_address_info.xml
+++ b/chrome/android/features/keyboard_accessory/internal/java/res/layout/keyboard_accessory_sheet_tab_address_info.xml
@@ -15,37 +15,12 @@
     android:layout_marginBottom="@dimen/keyboard_accessory_sheet_bottom_margin"
     android:orientation="vertical">
 
-    <LinearLayout
+    <org.chromium.ui.widget.ChipView
+        android:id="@+id/name_full"
         android:gravity="center_vertical|start"
-        android:fillViewport="true"
-        android:layout_height="@dimen/keyboard_accessory_suggestion_height"
-        android:layout_width="match_parent"
-        android:orientation="horizontal">
-
-        <org.chromium.ui.widget.ChipView
-            android:id="@+id/name_first"
-            android:gravity="center_vertical|start"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@style/InputChip" />
-
-        <org.chromium.ui.widget.ChipView
-            android:id="@+id/name_middle"
-            android:gravity="center_vertical|start"
-            android:layout_marginStart="@dimen/keyboard_accessory_sheet_padding"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@style/InputChip" />
-
-        <org.chromium.ui.widget.ChipView
-            android:id="@+id/name_last"
-            android:gravity="center_vertical|start"
-            android:layout_marginStart="@dimen/keyboard_accessory_sheet_padding"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@style/InputChip" />
-
-    </LinearLayout>
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="@style/InputChip" />
 
     <org.chromium.ui.widget.ChipView
         android:id="@+id/company_name"
@@ -78,6 +53,7 @@
         <org.chromium.ui.widget.ChipView
             android:id="@+id/address_home_state"
             android:gravity="center_vertical|start"
+            android:layout_marginEnd="@dimen/keyboard_accessory_sheet_padding"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             style="@style/InputChip" />
@@ -85,7 +61,7 @@
         <org.chromium.ui.widget.ChipView
             android:id="@+id/address_home_city"
             android:gravity="center_vertical|start"
-            android:layout_marginStart="@dimen/keyboard_accessory_sheet_padding"
+            android:layout_marginEnd="@dimen/keyboard_accessory_sheet_padding"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             style="@style/InputChip" />
@@ -93,7 +69,6 @@
         <org.chromium.ui.widget.ChipView
             android:id="@+id/address_home_zip"
             android:gravity="center_vertical|start"
-            android:layout_marginStart="@dimen/keyboard_accessory_sheet_padding"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             style="@style/InputChip" />
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryInfoView.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryInfoView.java
index 272ae28..1460611 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryInfoView.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryInfoView.java
@@ -18,9 +18,7 @@
  * This view represents a section of user data in the address tab of the keyboard accessory.
  */
 class AddressAccessoryInfoView extends LinearLayout {
-    private ChipView mNameFirst;
-    private ChipView mNameMiddle;
-    private ChipView mNameLast;
+    private ChipView mNameFull;
     private ChipView mCompanyName;
     private ChipView mAddressHomeLine1;
     private ChipView mAddressHomeLine2;
@@ -61,9 +59,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mNameFirst = findViewById(R.id.name_first);
-        mNameMiddle = findViewById(R.id.name_middle);
-        mNameLast = findViewById(R.id.name_last);
+        mNameFull = findViewById(R.id.name_full);
         mCompanyName = findViewById(R.id.company_name);
         mAddressHomeLine1 = findViewById(R.id.address_home_line_1);
         mAddressHomeLine2 = findViewById(R.id.address_home_line_2);
@@ -75,16 +71,8 @@
         mEmailAddress = findViewById(R.id.email_address);
     }
 
-    public ChipView getNameFirst() {
-        return mNameFirst;
-    }
-
-    public ChipView getNameMiddle() {
-        return mNameMiddle;
-    }
-
-    public ChipView getNameLast() {
-        return mNameLast;
+    public ChipView getNameFull() {
+        return mNameFull;
     }
 
     public ChipView getCompanyName() {
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewBinder.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewBinder.java
index 4ef49e9..dda8664 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewBinder.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewBinder.java
@@ -46,18 +46,16 @@
 
         @Override
         protected void bind(KeyboardAccessoryData.UserInfo info, AddressAccessoryInfoView view) {
-            bindChipView(view.getNameFirst(), info.getFields().get(0));
-            bindChipView(view.getNameMiddle(), info.getFields().get(1));
-            bindChipView(view.getNameLast(), info.getFields().get(2));
-            bindChipView(view.getCompanyName(), info.getFields().get(3));
-            bindChipView(view.getAddressHomeLine1(), info.getFields().get(4));
-            bindChipView(view.getAddressHomeLine2(), info.getFields().get(5));
-            bindChipView(view.getAddressHomeZip(), info.getFields().get(6));
-            bindChipView(view.getAddressHomeCity(), info.getFields().get(7));
-            bindChipView(view.getAddressHomeState(), info.getFields().get(8));
-            bindChipView(view.getAddressHomeCountry(), info.getFields().get(9));
-            bindChipView(view.getPhoneHomeWholeNumber(), info.getFields().get(10));
-            bindChipView(view.getEmailAddress(), info.getFields().get(11));
+            bindChipView(view.getNameFull(), info.getFields().get(0));
+            bindChipView(view.getCompanyName(), info.getFields().get(1));
+            bindChipView(view.getAddressHomeLine1(), info.getFields().get(2));
+            bindChipView(view.getAddressHomeLine2(), info.getFields().get(3));
+            bindChipView(view.getAddressHomeZip(), info.getFields().get(4));
+            bindChipView(view.getAddressHomeCity(), info.getFields().get(5));
+            bindChipView(view.getAddressHomeState(), info.getFields().get(6));
+            bindChipView(view.getAddressHomeCountry(), info.getFields().get(7));
+            bindChipView(view.getPhoneHomeWholeNumber(), info.getFields().get(8));
+            bindChipView(view.getEmailAddress(), info.getFields().get(9));
         }
 
         void bindChipView(ChipView chip, UserInfoField field) {
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryIntegrationTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryIntegrationTest.java
index 63ce6ae..4b06988 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryIntegrationTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryIntegrationTest.java
@@ -114,7 +114,7 @@
 
         // Focus the field to bring up the accessory.
         mHelper.focusPasswordField();
-        mHelper.waitForKeyboardAccessoryToBeShown(true);
+        mHelper.waitForKeyboardAccessoryToBeShown();
 
         // Click the tab to show the sheet and hide the keyboard.
         whenDisplayed(allOf(withContentDescription(R.string.address_accessory_sheet_toggle),
@@ -142,9 +142,10 @@
         whenDisplayed(withId(R.id.addresses_sheet));
 
         // Click a suggestion.
-        whenDisplayed(withText("McSpartangregor")).perform(click());
+        whenDisplayed(withText("Marcus McSpartangregor")).perform(click());
 
-        CriteriaHelper.pollInstrumentationThread(
-                () -> { return mHelper.getFieldText("NAME_FIRST").equals("McSpartangregor"); });
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            return mHelper.getFieldText("NAME_FIRST").equals("Marcus McSpartangregor");
+        });
     }
 }
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java
index 8a0d98e..1fddc52 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java
@@ -115,9 +115,7 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mModel.add(new AccessorySheetDataPiece(
                     createInfo(
-                            /*nameFirst=*/"Maya",
-                            /*nameMiddle=*/"J.",
-                            /*nameLast=*/"Park",
+                            /*nameFull=*/"Maya J. Park",
                             /*companyName=*/"",
                             /*addressHomeLine1=*/"100 Test Str.",
                             /*addressHomeLine2=*/"",
@@ -138,9 +136,7 @@
         CriteriaHelper.pollUiThread(() -> mView.get().getChildCount() > 0);
 
         // Check that the titles are correct:
-        assertThat(getChipText(R.id.name_first), is("Maya"));
-        assertThat(getChipText(R.id.name_middle), is("J."));
-        assertThat(getChipText(R.id.name_last), is("Park"));
+        assertThat(getChipText(R.id.name_full), is("Maya J. Park"));
         assertThat(getChipText(R.id.company_name), is(""));
         assertThat(getChipText(R.id.address_home_line_1), is("100 Test Str."));
         assertThat(getChipText(R.id.address_home_line_2), is(""));
@@ -155,25 +151,20 @@
         assertThat(findChipView(R.id.company_name).isShown(), is(false));
 
         // Chips are clickable:
-        TestThreadUtils.runOnUiThreadBlocking(findChipView(R.id.name_first)::performClick);
+        TestThreadUtils.runOnUiThreadBlocking(findChipView(R.id.name_full)::performClick);
         assertThat(clicked.get(), is(true));
         clicked.set(false);
         TestThreadUtils.runOnUiThreadBlocking(findChipView(R.id.email_address)::performClick);
         assertThat(clicked.get(), is(true));
     }
 
-    private UserInfo createInfo(String nameFirst, String nameMiddle, String nameLast,
-            String companyName, String addressHomeLine1, String addressHomeLine2,
-            String addressHomeZip, String addressHomeCity, String addressHomeState,
-            String addressHomeCountry, String phoneHomeWholeNumber, String emailAddress,
-            AtomicBoolean clickRecorder) {
+    private UserInfo createInfo(String nameFull, String companyName, String addressHomeLine1,
+            String addressHomeLine2, String addressHomeZip, String addressHomeCity,
+            String addressHomeState, String addressHomeCountry, String phoneHomeWholeNumber,
+            String emailAddress, AtomicBoolean clickRecorder) {
         UserInfo info = new UserInfo("", null);
-        info.addField(new UserInfoField(
-                nameFirst, nameFirst, "", false, item -> clickRecorder.set(true)));
-        info.addField(new UserInfoField(
-                nameMiddle, nameMiddle, "", false, item -> clickRecorder.set(true)));
         info.addField(
-                new UserInfoField(nameLast, nameLast, "", false, item -> clickRecorder.set(true)));
+                new UserInfoField(nameFull, nameFull, "", false, item -> clickRecorder.set(true)));
         info.addField(new UserInfoField(
                 companyName, companyName, "", false, item -> clickRecorder.set(true)));
         info.addField(new UserInfoField(
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessoryIntegrationTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessoryIntegrationTest.java
index 0dc6ce1..f3ef0c3 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessoryIntegrationTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessoryIntegrationTest.java
@@ -119,7 +119,7 @@
 
         // Focus the field to bring up the accessory.
         mHelper.focusPasswordField();
-        mHelper.waitForKeyboardAccessoryToBeShown(true);
+        mHelper.waitForKeyboardAccessoryToBeShown();
 
         // Click the tab to show the sheet and hide the keyboard.
         whenDisplayed(allOf(withContentDescription(R.string.credit_card_accessory_sheet_toggle),
diff --git a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/ArConsentDialog.java b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/ArConsentDialog.java
index 1a268a5..29f9031 100644
--- a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/ArConsentDialog.java
+++ b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/ArConsentDialog.java
@@ -142,6 +142,6 @@
 
     @NativeMethods
     /* package */ interface Natives {
-        void onUserConsentResult(long nativeArcoreConsentPrompt, boolean allowed);
+        void onUserConsentResult(long nativeArCoreConsentPrompt, boolean allowed);
     }
 }
diff --git a/chrome/android/java/res/layout/location_status_icon.xml b/chrome/android/java/res/layout/location_status_icon.xml
index 7f23056..0310480 100644
--- a/chrome/android/java/res/layout/location_status_icon.xml
+++ b/chrome/android/java/res/layout/location_status_icon.xml
@@ -8,11 +8,8 @@
 
     <org.chromium.ui.widget.ChromeImageButton
         android:id="@+id/location_bar_status_icon"
-        style="@style/LocationBarButton"
-        android:layout_width="@dimen/location_bar_start_icon_width"
-        android:layout_height="match_parent"
+        style="@style/OmniboxIcon"
         android:layout_gravity="center"
-        android:paddingEnd="4dp"
         android:src="@android:color/transparent"
         android:visibility="gone"
         android:contentDescription="@string/accessibility_toolbar_btn_site_info"
diff --git a/chrome/android/java/res/layout/new_tab_page_layout.xml b/chrome/android/java/res/layout/new_tab_page_layout.xml
index 9a138cd6..7ab7a8b 100644
--- a/chrome/android/java/res/layout/new_tab_page_layout.xml
+++ b/chrome/android/java/res/layout/new_tab_page_layout.xml
@@ -40,6 +40,13 @@
         android:background="@drawable/ntp_search_box"
         android:orientation="horizontal"
         android:paddingStart="@dimen/location_bar_lateral_padding" >
+        <ImageView
+            style="@style/OmniboxIcon"
+            android:id="@+id/search_engine_logo"
+            android:visibility="gone"
+            android:importantForAccessibility="no"
+            android:src="@drawable/ic_logo_googleg_24dp"
+            android:scaleType="centerInside" />
         <!-- TODO(crbug.com/900912): Fix and remove lint ignore -->
         <EditText
             tools:ignore="Autofill,LabelFor"
diff --git a/chrome/android/java/res/layout/revamped_context_menu_header.xml b/chrome/android/java/res/layout/revamped_context_menu_header.xml
index 47916bd..2cdbba3 100644
--- a/chrome/android/java/res/layout/revamped_context_menu_header.xml
+++ b/chrome/android/java/res/layout/revamped_context_menu_header.xml
@@ -13,9 +13,7 @@
     android:layout_height="wrap_content"
     android:orientation="horizontal"
     android:paddingStart="@dimen/revamped_context_menu_list_lateral_padding"
-    android:paddingEnd="@dimen/revamped_context_menu_list_lateral_padding"
-    android:paddingTop="@dimen/revamped_context_menu_header_vertical_padding"
-    android:paddingBottom="@dimen/revamped_context_menu_header_vertical_padding">
+    android:paddingEnd="@dimen/revamped_context_menu_list_lateral_padding">
 
     <FrameLayout
         android:layout_width="wrap_content"
@@ -28,7 +26,9 @@
             android:background="@drawable/tile_view_icon_background_modern"
             android:layout_width="@dimen/revamped_context_menu_header_circle_bg_diameter"
             android:layout_height="@dimen/revamped_context_menu_header_circle_bg_diameter"
-            android:layout_margin="@dimen/revamped_context_menu_header_circle_bg_margin"
+            android:layout_marginTop="@dimen/revamped_context_menu_header_circle_bg_vertical_margin"
+            android:layout_marginStart="@dimen/revamped_context_menu_header_circle_bg_lateral_margin"
+            android:layout_marginEnd="@dimen/revamped_context_menu_header_circle_bg_lateral_margin"
             android:visibility="invisible" />
 
         <org.chromium.ui.widget.RoundedCornerImageView
@@ -37,6 +37,8 @@
             android:layout_height="@dimen/revamped_context_menu_header_image_max_size"
             android:scaleType="centerInside"
             android:importantForAccessibility="no"
+            android:layout_marginTop="@dimen/revamped_context_menu_header_vertical_padding"
+            android:layout_marginBottom="@dimen/revamped_context_menu_header_vertical_padding"
             app:cornerRadiusTopStart="@dimen/default_rounded_corner_radius"
             app:cornerRadiusTopEnd="@dimen/default_rounded_corner_radius"
             app:cornerRadiusBottomStart="@dimen/default_rounded_corner_radius"
@@ -45,10 +47,13 @@
     </FrameLayout>
 
     <LinearLayout
+        android:id="@+id/title_and_url"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical"
-        android:layout_gravity="center_vertical">
+        android:layout_gravity="center_vertical"
+        android:paddingTop="@dimen/revamped_context_menu_header_vertical_padding"
+        android:paddingBottom="@dimen/revamped_context_menu_header_vertical_padding">
 
         <org.chromium.ui.widget.TextViewWithLeading
             android:id="@+id/menu_header_title"
diff --git a/chrome/android/java/res/values-v17/styles.xml b/chrome/android/java/res/values-v17/styles.xml
index 8f9ac197..88c1c9a 100644
--- a/chrome/android/java/res/values-v17/styles.xml
+++ b/chrome/android/java/res/values-v17/styles.xml
@@ -1009,6 +1009,12 @@
         <item name="tint">@color/default_icon_color</item>
     </style>
 
+    <style name="OmniboxIcon" parent="LocationBarButton">
+        <item name="android:layout_width">@dimen/location_bar_start_icon_width</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_marginEnd">4dp</item>
+    </style>
+
     <!-- Revamped context menu -->
     <style name="RevampedContextMenuItemText">
         <item name="android:background">?attr/selectableItemBackground</item>
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index dded95b..5582f20 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -573,7 +573,8 @@
     <dimen name="revamped_context_menu_divider_padding">8dp</dimen>
     <dimen name="revamped_context_menu_header_image_max_size">60dp</dimen>
     <dimen name="revamped_context_menu_header_circle_bg_diameter">48dp</dimen>
-    <dimen name="revamped_context_menu_header_circle_bg_margin">6dp</dimen>
+    <dimen name="revamped_context_menu_header_circle_bg_lateral_margin">6dp</dimen>
+    <dimen name="revamped_context_menu_header_circle_bg_vertical_margin">22dp</dimen>
     <dimen name="revamped_context_menu_header_monogram_text_size">16dp</dimen>
     <dimen name="revamped_context_menu_header_monogram_size">26dp</dimen>
     <!-- Reader Mode dimensions -->
diff --git a/chrome/android/java/res/xml/contextual_search_preferences.xml b/chrome/android/java/res/xml/contextual_search_preferences.xml
index f20942c..4bd9f7b 100644
--- a/chrome/android/java/res/xml/contextual_search_preferences.xml
+++ b/chrome/android/java/res/xml/contextual_search_preferences.xml
@@ -3,17 +3,17 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<PreferenceScreen
+<android.support.v7.preference.PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto">
 
-    <org.chromium.chrome.browser.preferences.ChromeSwitchPreference
+    <org.chromium.chrome.browser.preferences.ChromeSwitchPreferenceCompat
         android:key="contextual_search_switch"
         android:summaryOn="@string/text_on"
-        android:summaryOff="@string/text_off"
-        app:drawDivider="true" />
+        android:summaryOff="@string/text_off" />
 
-    <org.chromium.chrome.browser.preferences.TextMessagePreference
-        android:title="@string/contextual_search_description" />
+    <org.chromium.chrome.browser.preferences.TextMessagePreferenceCompat
+        android:title="@string/contextual_search_description"
+        app:allowDividerBelow="false" />
 
-</PreferenceScreen>
+</android.support.v7.preference.PreferenceScreen>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java
index cf2a02f..9fcf46b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java
@@ -121,12 +121,12 @@
 
             // Record via UMA all modules that have been requested and are currently installed. This
             // will tell us the install penetration of each module over time.
-            ModuleInstaller.recordModuleAvailability();
+            ModuleInstaller.getInstance().recordModuleAvailability();
         }
 
         // Write installed modules to crash keys. This needs to be done as early as possible so that
         // these values are set before any crashes are reported.
-        ModuleInstaller.updateCrashKeys();
+        ModuleInstaller.getInstance().updateCrashKeys();
 
         BuildInfo.setFirebaseAppId(FirebaseConfig.getFirebaseAppId());
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index 81433226..1c1b4a0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -284,6 +284,7 @@
     public static final String OMNIBOX_SHOW_SUGGESTION_FAVICONS =
             "OmniboxUIExperimentShowSuggestionFavicons";
     public static final String OMNIBOX_SPARE_RENDERER = "OmniboxSpareRenderer";
+    public static final String OMNIBOX_SEARCH_ENGINE_LOGO = "OmniboxSearchEngineLogo";
     public static final String OVERLAY_NEW_LAYOUT = "OverlayNewLayout";
     public static final String OVERSCROLL_HISTORY_NAVIGATION = "OverscrollHistoryNavigation";
     public static final String PASSWORD_EDITING_ANDROID = "PasswordEditingAndroid";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java
index 15343d4..e742931 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java
@@ -773,6 +773,7 @@
         List<String> names = new ArrayList<>();
         List<String> shortNames = new ArrayList<>();
         List<String> packageNames = new ArrayList<>();
+        List<String> ids = new ArrayList<>();
         List<Integer> shellApkVersions = new ArrayList<>();
         List<Integer> versionCodes = new ArrayList<>();
         List<String> uris = new ArrayList<>();
@@ -785,6 +786,7 @@
         List<Long> backgroundColors = new ArrayList<>();
         List<Long> lastUpdateCheckTimesMs = new ArrayList<>();
         List<Boolean> relaxUpdates = new ArrayList<>();
+        List<String> updateStatuses = new ArrayList<>();
 
         Context context = ContextUtils.getApplicationContext();
         PackageManager packageManager = context.getPackageManager();
@@ -799,6 +801,7 @@
                     names.add(webApkInfo.name());
                     shortNames.add(webApkInfo.shortName());
                     packageNames.add(webApkInfo.webApkPackageName());
+                    ids.add(webApkInfo.id());
                     shellApkVersions.add(webApkInfo.shellApkVersion());
                     versionCodes.add(packageInfo.versionCode);
                     uris.add(webApkInfo.uri().toString());
@@ -821,12 +824,13 @@
                     }
                     lastUpdateCheckTimesMs.add(lastUpdateCheckTimeMsForStorage);
                     relaxUpdates.add(relaxUpdatesForStorage);
+                    updateStatuses.add(storage.getUpdateStatus());
                 }
             }
         }
         nativeOnWebApksRetrieved(callbackPointer, names.toArray(new String[0]),
                 shortNames.toArray(new String[0]), packageNames.toArray(new String[0]),
-                CollectionUtil.integerListToIntArray(shellApkVersions),
+                ids.toArray(new String[0]), CollectionUtil.integerListToIntArray(shellApkVersions),
                 CollectionUtil.integerListToIntArray(versionCodes), uris.toArray(new String[0]),
                 scopes.toArray(new String[0]), manifestUrls.toArray(new String[0]),
                 manifestStartUrls.toArray(new String[0]),
@@ -835,13 +839,23 @@
                 CollectionUtil.longListToLongArray(themeColors),
                 CollectionUtil.longListToLongArray(backgroundColors),
                 CollectionUtil.longListToLongArray(lastUpdateCheckTimesMs),
-                CollectionUtil.booleanListToBooleanArray(relaxUpdates));
+                CollectionUtil.booleanListToBooleanArray(relaxUpdates),
+                updateStatuses.toArray(new String[0]));
+    }
+
+    @CalledByNative
+    public static void setForceWebApkUpdate(String id) {
+        WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(id);
+        if (storage != null) {
+            storage.setShouldForceUpdate(true);
+        }
     }
 
     private static native void nativeOnWebappDataStored(long callbackPointer);
     private static native void nativeOnWebApksRetrieved(long callbackPointer, String[] names,
-            String[] shortNames, String[] packageName, int[] shellApkVersions, int[] versionCodes,
-            String[] uris, String[] scopes, String[] manifestUrls, String[] manifestStartUrls,
-            int[] displayModes, int[] orientations, long[] themeColors, long[] backgroundColors,
-            long[] lastUpdateCheckTimesMs, boolean[] relaxUpdates);
+            String[] shortNames, String[] packageNames, String[] ids, int[] shellApkVersions,
+            int[] versionCodes, String[] uris, String[] scopes, String[] manifestUrls,
+            String[] manifestStartUrls, int[] displayModes, int[] orientations, long[] themeColors,
+            long[] backgroundColors, long[] lastUpdateCheckTimesMs, boolean[] relaxUpdates,
+            String[] updateStatuses);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPromoControl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPromoControl.java
index f28bcc3..2955971 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPromoControl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPromoControl.java
@@ -386,7 +386,7 @@
         new Handler().post(new Runnable() {
             @Override
             public void run() {
-                PreferencesLauncher.launchSettingsPage(
+                PreferencesLauncher.launchSettingsPageCompat(
                         getContext(), ContextualSearchPreferenceFragment.class);
             }
         });
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewBinder.java
index 1470c4d..420e5a5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewBinder.java
@@ -34,7 +34,7 @@
                             ? View.GONE
                             : View.VISIBLE);
         } else if (propertyKey == RevampedContextMenuHeaderProperties.URL_CLICK_LISTENER) {
-            view.findViewById(R.id.menu_header_url)
+            view.findViewById(R.id.title_and_url)
                     .setOnClickListener(
                             model.get(RevampedContextMenuHeaderProperties.URL_CLICK_LISTENER));
         } else if (propertyKey == RevampedContextMenuHeaderProperties.URL_MAX_LINES) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/BluetoothChooserDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialog.java
similarity index 99%
rename from chrome/android/java/src/org/chromium/chrome/browser/BluetoothChooserDialog.java
rename to chrome/android/java/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialog.java
index 913a37a..44e5778 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/BluetoothChooserDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialog.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.Manifest;
 import android.app.Activity;
@@ -26,6 +26,7 @@
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeBaseAppCompatActivity;
 import org.chromium.chrome.browser.omnibox.OmniboxUrlEmphasizer;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.location.LocationUtils;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialog.java
similarity index 98%
rename from chrome/android/java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java
rename to chrome/android/java/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialog.java
index 572c312..bcdb010e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialog.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.app.Activity;
 import android.app.Dialog;
@@ -27,6 +27,7 @@
 import org.chromium.base.annotations.JCaller;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeBaseAppCompatActivity;
 import org.chromium.chrome.browser.omnibox.OmniboxUrlEmphasizer;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.util.MathUtils;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DeviceItemAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/DeviceItemAdapter.java
similarity index 99%
rename from chrome/android/java/src/org/chromium/chrome/browser/DeviceItemAdapter.java
rename to chrome/android/java/src/org/chromium/chrome/browser/device_dialog/DeviceItemAdapter.java
index ebd7bd1..42710ea 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DeviceItemAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/DeviceItemAdapter.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.content.Context;
 import android.content.res.Resources;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DeviceItemRow.java b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/DeviceItemRow.java
similarity index 97%
rename from chrome/android/java/src/org/chromium/chrome/browser/DeviceItemRow.java
rename to chrome/android/java/src/org/chromium/chrome/browser/device_dialog/DeviceItemRow.java
index 2e907e2..8b2ffe4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DeviceItemRow.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/DeviceItemRow.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.graphics.drawable.Drawable;
 import android.os.Build;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ItemChooserDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialog.java
similarity index 91%
rename from chrome/android/java/src/org/chromium/chrome/browser/ItemChooserDialog.java
rename to chrome/android/java/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialog.java
index 4ff29f0..77205cf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ItemChooserDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialog.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.app.Activity;
 import android.app.Dialog;
@@ -18,6 +18,7 @@
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.Window;
+import android.widget.AdapterView;
 import android.widget.Button;
 import android.widget.LinearLayout;
 import android.widget.ListView;
@@ -26,6 +27,7 @@
 
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.chrome.browser.util.MathUtils;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.widget.TextViewWithClickableSpans;
@@ -170,20 +172,40 @@
         mConfirmButton = (Button) dialogContainer.findViewById(R.id.positive);
         mConfirmButton.setText(labels.positiveButton);
         mConfirmButton.setEnabled(false);
-        mConfirmButton.setOnClickListener(v -> {
-            mItemSelectedCallback.onItemSelected(mItemAdapter.getSelectedItemKey());
-            mDialog.setOnDismissListener(null);
-            mDialog.dismiss();
-        });
+
+        View.OnClickListener clickListener = new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mItemSelectedCallback.onItemSelected(mItemAdapter.getSelectedItemKey());
+                mDialog.setOnDismissListener(null);
+                mDialog.dismiss();
+            }
+        };
 
         mItemAdapter = new DeviceItemAdapter(
                 mActivity, /*itemsSelectable=*/true, R.layout.item_chooser_dialog_row);
         mItemAdapter.setNotifyOnChange(true);
         mItemAdapter.setObserver(this);
+
+        if (FeatureUtilities.isNoTouchModeEnabled()) {
+            // TODO(crbug.com/982869): ideally we would port to using the modal dialog
+            // manager. Until then, we will treat clicking on the items as selecting them.
+            mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+                @Override
+                public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
+                    mItemAdapter.onItemClick(adapter, view, position, id);
+                    clickListener.onClick(null);
+                }
+            });
+            mConfirmButton.setVisibility(View.GONE);
+        } else {
+            mConfirmButton.setOnClickListener(clickListener);
+            mListView.setOnItemClickListener(mItemAdapter);
+        }
+
         mListView.setAdapter(mItemAdapter);
         mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
         mListView.setEmptyView(mEmptyMessage);
-        mListView.setOnItemClickListener(mItemAdapter);
         mListView.setDivider(null);
         setState(State.STARTING);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/OWNERS
new file mode 100644
index 0000000..298cd1c
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/OWNERS
@@ -0,0 +1,2 @@
+juncai@chromium.org
+reillyg@chromium.org
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/UsbChooserDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialog.java
similarity index 98%
rename from chrome/android/java/src/org/chromium/chrome/browser/UsbChooserDialog.java
rename to chrome/android/java/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialog.java
index 02668b0..23cd8ad 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/UsbChooserDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialog.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.app.Activity;
 import android.text.SpannableString;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
index a854524..2a6c9dc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
@@ -39,6 +39,7 @@
 import org.chromium.chrome.browser.ntp.NewTabPage.OnSearchBoxScrollListener;
 import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager;
 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
+import org.chromium.chrome.browser.omnibox.LocationBarLayout;
 import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.suggestions.SiteSuggestion;
@@ -282,6 +283,16 @@
 
         manager.addDestructionObserver(NewTabPageLayout.this::onDestroy);
 
+        // Native will be initialized at this time (see NativePageFactory.java).
+        // TODO(crbug.com/982430): Check for changes to the DSE and react accordingly.
+        // TODO(crbug.com/973150): Fetch the favicon when the DSE isn't Google.
+        if (LocationBarLayout.shouldShowGoogleLogo()) {
+            ImageView logoView = findViewById(R.id.search_engine_logo);
+
+            assert logoView != null;
+            logoView.setVisibility(VISIBLE);
+        }
+
         mInitialized = true;
 
         TraceEvent.end(TAG + ".initialize()");
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
index 6db6ed6..f5ae08c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
@@ -32,6 +32,7 @@
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.WindowDelegate;
 import org.chromium.chrome.browser.locale.LocaleManager;
@@ -85,8 +86,6 @@
     protected ToolbarDataProvider mToolbarDataProvider;
     private ObserverList<UrlFocusChangeListener> mUrlFocusChangeListeners = new ObserverList<>();
 
-    protected boolean mNativeInitialized;
-
     private final List<Runnable> mDeferredNativeRunnables = new ArrayList<Runnable>();
 
     protected StatusViewCoordinator mStatusViewCoordinator;
@@ -96,11 +95,12 @@
     private WindowAndroid mWindowAndroid;
     private WindowDelegate mWindowDelegate;
 
-    private boolean mUrlHasFocus;
     protected boolean mUrlFocusChangeInProgress;
+    protected boolean mNativeInitialized;
+    protected boolean mShouldShowGoogleLogo;
+    private boolean mUrlHasFocus;
     private boolean mUrlFocusedFromFakebox;
     private boolean mUrlFocusedWithoutAnimations;
-
     private boolean mVoiceSearchEnabled;
 
     private OmniboxPrerender mOmniboxPrerender;
@@ -140,6 +140,7 @@
             return false;
         }
     }
+
     public LocationBarLayout(Context context, AttributeSet attrs) {
         this(context, attrs, R.layout.location_bar);
     }
@@ -289,6 +290,16 @@
     }
 
     /**
+     * Encapsulates complicated boolean check for reuse and readability.
+     */
+    public static boolean shouldShowGoogleLogo() {
+        return !LocaleManager.getInstance().needToCheckForSearchEnginePromo()
+                && TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle()
+                && ChromeFeatureList.isInitialized()
+                && ChromeFeatureList.isEnabled(ChromeFeatureList.OMNIBOX_SEARCH_ENGINE_LOGO);
+    }
+
+    /**
      * Handles native dependent initialization for this class.
      */
     @Override
@@ -308,6 +319,15 @@
         }
         mDeferredNativeRunnables.clear();
 
+        // Show the DSE logo when the url has focus.
+        // TODO(crbug.com/982430): Check for changes to the DSE and react accordingly.
+        mShouldShowGoogleLogo = LocationBarLayout.shouldShowGoogleLogo();
+        mStatusViewCoordinator.setShouldShowGoogleLogo(mShouldShowGoogleLogo);
+        mToolbarDataProvider.setShouldShowGoogleLogo(mShouldShowGoogleLogo);
+        if (mShouldShowGoogleLogo) {
+            mStatusViewCoordinator.setShowIconsWhenUrlFocused(true);
+        }
+
         updateVisualsForState();
 
         updateMicButtonVisibility(mUrlFocusChangePercent);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
index bd0dd563..7defcab6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
@@ -39,7 +39,10 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mFirstVisibleFocusedView = findViewById(R.id.url_bar);
+        // Assign the first visible view here only if it hasn't been set by the DSE icon experiment.
+        // See onNativeLibrary ready for when this variable is set for the DSE icon case.
+        mFirstVisibleFocusedView = mFirstVisibleFocusedView == null ? findViewById(R.id.url_bar)
+                                                                    : mFirstVisibleFocusedView;
 
         Rect delegateArea = new Rect();
         mUrlActionContainer.getHitRect(delegateArea);
@@ -49,6 +52,16 @@
         setTouchDelegate(touchDelegate);
     }
 
+    @Override
+    public void onNativeLibraryReady() {
+        super.onNativeLibraryReady();
+
+        // The search logo will be the first visible view when the google logo is showing.
+        if (mShouldShowGoogleLogo) {
+            mFirstVisibleFocusedView = findViewById(R.id.location_bar_status);
+        }
+    }
+
     /**
      * @return The first view visible when the location bar is focused.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
index 4830f8d..37a8795 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
@@ -27,6 +27,7 @@
     private boolean mVerboseStatusSpaceAvailable;
     private boolean mPageIsPreview;
     private boolean mPageIsOffline;
+    private boolean mShouldShowGoogleLogo;
 
     private boolean mShowStatusIconWhenUrlFocused;
 
@@ -281,6 +282,14 @@
     }
 
     /**
+     * Turn on/off the google logo in the omnibox.
+     */
+    void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo) {
+        mShouldShowGoogleLogo = shouldShowGoogleLogo;
+        updateLocationBarIcon();
+    }
+
+    /**
      * Update selection of icon presented on the location bar.
      *
      * - Navigation button is:
@@ -301,10 +310,15 @@
 
         if (mUrlHasFocus) {
             if (mShowStatusIconWhenUrlFocused) {
-                icon = mFirstSuggestionIsSearchQuery ? R.drawable.omnibox_search
-                                                     : R.drawable.ic_omnibox_page;
-                tint = mNavigationIconTintRes;
-                description = R.string.accessibility_toolbar_btn_site_info;
+                if (mShouldShowGoogleLogo) {
+                    // TODO(crbug.com/973150): Fetch the favicon when the DSE isn't Google.
+                    icon = R.drawable.ic_logo_googleg_24dp;
+                } else {
+                    icon = mFirstSuggestionIsSearchQuery ? R.drawable.omnibox_search
+                                                         : R.drawable.ic_omnibox_page;
+                    tint = mNavigationIconTintRes;
+                    description = R.string.accessibility_toolbar_btn_site_info;
+                }
             }
         } else if (mSecurityIconRes != 0) {
             mIsSecurityButtonShown = true;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java
index f815177..1fab538 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java
@@ -194,4 +194,11 @@
     public void setFirstSuggestionIsSearchType(boolean firstSuggestionIsSearchQuery) {
         mMediator.setFirstSuggestionIsSearchType(firstSuggestionIsSearchQuery);
     }
+
+    /**
+     * Turn on/off the google logo in the omnibox.
+     */
+    public void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo) {
+        mMediator.setShouldShowGoogleLogo(shouldShowGoogleLogo);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinder.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinder.java
index 2d7de92..5d866a2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinder.java
@@ -391,6 +391,11 @@
     }
 
     @Override
+    public void onVerificationError(String errorMessage) {
+        mCallback.onGetPaymentAppsError(errorMessage);
+    }
+
+    @Override
     public void onFinishedVerification() {
         mPendingVerifiersCount--;
         if (mPendingVerifiersCount != 0) return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java
index 06d181d..3946823 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java
@@ -38,6 +38,12 @@
         void onPaymentAppCreated(PaymentApp paymentApp);
 
         /**
+         * Called when an error has occurred.
+         * @param errorMessage Developer facing error message.
+         */
+        void onGetPaymentAppsError(String errorMessage);
+
+        /**
          * Called when the factory is finished creating payment apps.
          */
         void onAllPaymentAppsCreated();
@@ -119,6 +125,11 @@
                 }
 
                 @Override
+                public void onGetPaymentAppsError(String errorMessage) {
+                    callback.onGetPaymentAppsError(errorMessage);
+                }
+
+                @Override
                 public void onAllPaymentAppsCreated() {
                     mPendingTasks.remove(additionalFactory);
                     if (mPendingTasks.isEmpty()) callback.onAllPaymentAppsCreated();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java
index 8c0ec0b..d2e1dba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java
@@ -70,6 +70,13 @@
         void onAllOriginsSupported(URI methodName);
 
         /**
+         * Called when a part of verification has failed.
+         *
+         * @param errorMessage Developer facing error message.
+         */
+        void onVerificationError(String errorMessage);
+
+        /**
          * Called when the manifest has been fully verified. No more valid apps or origins will
          * be found after this call.
          */
@@ -524,10 +531,12 @@
     }
 
     @Override
-    public void onManifestDownloadFailure() {
+    public void onManifestDownloadFailure(String errorMessage) {
         if (mAtLeastOneManifestFailedToDownloadOrParse) return;
         mAtLeastOneManifestFailedToDownloadOrParse = true;
 
+        mCallback.onVerificationError(errorMessage);
+
         if (mIsManifestCacheStaleOrUnusable) mCallback.onFinishedVerification();
         mCallback.onFinishedUsingResources();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index 20bd792b..1b2eb70 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -409,7 +409,13 @@
     private PaymentResponseHelper mPaymentResponseHelper;
 
     /** If not empty, use this error message for rejecting PaymentRequest.show(). */
-    private String mProhibitedOriginOrInvalidSslErrorMessage;
+    private String mRejectShowErrorMessage;
+
+    /**
+     * True when Payment Request is invoked on a prohibited origin (e.g., blob:) or with an invalid
+     * SSL certificate (e.g., self-signed).
+     */
+    private boolean mIsProhibitedOriginOrInvalidSsl;
 
     /**
      * Builds the PaymentRequest service implementation.
@@ -488,8 +494,9 @@
         // TODO(crbug.com/978471): Improve architecture for handling prohibited origins and invalid
         // SSL certificates.
         if (!UrlUtil.isOriginAllowedToUseWebPaymentApis(mWebContents.getLastCommittedUrl())) {
-            mProhibitedOriginOrInvalidSslErrorMessage = ErrorStrings.PROHIBITED_ORIGIN;
-            Log.d(TAG, mProhibitedOriginOrInvalidSslErrorMessage);
+            mIsProhibitedOriginOrInvalidSsl = true;
+            mRejectShowErrorMessage = ErrorStrings.PROHIBITED_ORIGIN;
+            Log.d(TAG, mRejectShowErrorMessage);
             Log.d(TAG, ErrorStrings.PROHIBITED_ORIGIN_OR_INVALID_SSL_EXPLANATION);
             // Don't show any UI. Resolve .canMakePayment() with "false". Reject .show() with
             // "NotSupportedError".
@@ -500,10 +507,11 @@
         mJourneyLogger.setRequestedInformation(
                 mRequestShipping, mRequestPayerEmail, mRequestPayerPhone, mRequestPayerName);
 
-        mProhibitedOriginOrInvalidSslErrorMessage =
-                mDelegate.getInvalidSslCertificateErrorMessage(mWebContents);
-        if (!TextUtils.isEmpty(mProhibitedOriginOrInvalidSslErrorMessage)) {
-            Log.d(TAG, mProhibitedOriginOrInvalidSslErrorMessage);
+        assert mRejectShowErrorMessage == null;
+        mRejectShowErrorMessage = mDelegate.getInvalidSslCertificateErrorMessage(mWebContents);
+        if (!TextUtils.isEmpty(mRejectShowErrorMessage)) {
+            mIsProhibitedOriginOrInvalidSsl = true;
+            Log.d(TAG, mRejectShowErrorMessage);
             Log.d(TAG, ErrorStrings.PROHIBITED_ORIGIN_OR_INVALID_SSL_EXPLANATION);
             // Don't show any UI. Resolve .canMakePayment() with "false". Reject .show() with
             // "NotSupportedError".
@@ -811,6 +819,11 @@
     }
 
     @Override
+    public void onGetPaymentAppsError(String errorMessage) {
+        if (TextUtils.isEmpty(mRejectShowErrorMessage)) mRejectShowErrorMessage = errorMessage;
+    }
+
+    @Override
     public void onAllPaymentAppsCreated() {
         if (mClient == null) return;
 
@@ -2117,11 +2130,11 @@
             mJourneyLogger.setNotShown(mArePaymentMethodsSupported
                             ? NotShownReason.NO_MATCHING_PAYMENT_METHOD
                             : NotShownReason.NO_SUPPORTED_PAYMENT_METHOD);
-            if (!TextUtils.isEmpty(mProhibitedOriginOrInvalidSslErrorMessage)) {
+            if (mIsProhibitedOriginOrInvalidSsl) {
                 if (mNativeObserverForTest != null) mNativeObserverForTest.onNotSupportedError();
                 // Chrome always refuses payments with invalid SSL and in prohibited origin types.
-                disconnectFromClientWithDebugMessage(mProhibitedOriginOrInvalidSslErrorMessage,
-                        PaymentErrorReason.NOT_SUPPORTED);
+                disconnectFromClientWithDebugMessage(
+                        mRejectShowErrorMessage, PaymentErrorReason.NOT_SUPPORTED);
             } else if (mIsIncognito) {
                 // If the user is in the incognito mode, hide the absence of their payment methods
                 // from the merchant site.
@@ -2130,7 +2143,10 @@
             } else {
                 if (mNativeObserverForTest != null) mNativeObserverForTest.onNotSupportedError();
                 disconnectFromClientWithDebugMessage(
-                        ErrorStrings.GENERIC_PAYMENT_METHOD_NOT_SUPPORTED_MESSAGE,
+                        ErrorStrings.GENERIC_PAYMENT_METHOD_NOT_SUPPORTED_MESSAGE
+                                + (TextUtils.isEmpty(mRejectShowErrorMessage)
+                                                ? ""
+                                                : " " + mRejectShowErrorMessage),
                         PaymentErrorReason.NOT_SUPPORTED);
             }
             if (sObserverForTest != null) sObserverForTest.onPaymentRequestServiceShowFailed();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
index 504944c..913e3b1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
@@ -403,6 +403,14 @@
     }
 
     @CalledByNative
+    private static void onGetPaymentAppsError(
+            PaymentAppFactory.PaymentAppCreatedCallback callback, String errorMessage) {
+        ThreadUtils.assertOnUiThread();
+
+        callback.onGetPaymentAppsError(errorMessage);
+    }
+
+    @CalledByNative
     private static void onHasServiceWorkerPaymentApps(
             HasServiceWorkerPaymentAppsCallback callback, boolean hasPaymentApps) {
         ThreadUtils.assertOnUiThread();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferences.java
index 07e93f1..7a667fc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferences.java
@@ -4,44 +4,42 @@
 
 package org.chromium.chrome.browser.preferences.autofill_assistant;
 
+import android.content.Context;
 import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.PreferenceFragment;
+import android.support.v7.preference.PreferenceFragmentCompat;
+import android.support.v7.preference.PreferenceScreen;
 
 import org.chromium.base.ContextUtils;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.preferences.ChromeSwitchPreference;
+import org.chromium.chrome.browser.preferences.ChromeSwitchPreferenceCompat;
 
 /** The "Autofill Assistant" preferences screen in Settings. */
-public class AutofillAssistantPreferences extends PreferenceFragment {
+public class AutofillAssistantPreferences extends PreferenceFragmentCompat {
     /** Autofill Assistant switch preference key name. */
     public static final String PREF_AUTOFILL_ASSISTANT_SWITCH = "autofill_assistant_switch";
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         getActivity().setTitle(R.string.prefs_autofill_assistant_title);
-        setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity()));
+
+        PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(getStyledContext());
+        setPreferenceScreen(screen);
         createAutofillAssistantSwitch();
     }
 
     private void createAutofillAssistantSwitch() {
-        ChromeSwitchPreference autofillAssistantSwitch =
-                new ChromeSwitchPreference(getActivity(), null);
+        ChromeSwitchPreferenceCompat autofillAssistantSwitch =
+                new ChromeSwitchPreferenceCompat(getStyledContext(), null);
         autofillAssistantSwitch.setKey(PREF_AUTOFILL_ASSISTANT_SWITCH);
         autofillAssistantSwitch.setTitle(R.string.prefs_autofill_assistant_switch);
         autofillAssistantSwitch.setSummaryOn(R.string.text_on);
         autofillAssistantSwitch.setSummaryOff(R.string.text_off);
-        autofillAssistantSwitch.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
-            @Override
-            public boolean onPreferenceChange(Preference preference, Object newValue) {
-                ContextUtils.getAppSharedPreferences()
-                        .edit()
-                        .putBoolean(PREF_AUTOFILL_ASSISTANT_SWITCH, (boolean) newValue)
-                        .apply();
-                return true;
-            }
+        autofillAssistantSwitch.setOnPreferenceChangeListener((preference, newValue) -> {
+            ContextUtils.getAppSharedPreferences()
+                    .edit()
+                    .putBoolean(PREF_AUTOFILL_ASSISTANT_SWITCH, (boolean) newValue)
+                    .apply();
+            return true;
         });
         getPreferenceScreen().addPreference(autofillAssistantSwitch);
 
@@ -52,4 +50,8 @@
         autofillAssistantSwitch.setChecked(ContextUtils.getAppSharedPreferences().getBoolean(
                 PREF_AUTOFILL_ASSISTANT_SWITCH, true));
     }
+
+    private Context getStyledContext() {
+        return getPreferenceManager().getContext();
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/ContextualSearchPreferenceFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/ContextualSearchPreferenceFragment.java
index 10355f7..3988888 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/ContextualSearchPreferenceFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/ContextualSearchPreferenceFragment.java
@@ -5,26 +5,22 @@
 package org.chromium.chrome.browser.preferences.privacy;
 
 import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.PreferenceFragment;
+import android.support.v7.preference.PreferenceFragmentCompat;
 
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchUma;
-import org.chromium.chrome.browser.preferences.ChromeSwitchPreference;
+import org.chromium.chrome.browser.preferences.ChromeSwitchPreferenceCompat;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
 import org.chromium.chrome.browser.preferences.PreferenceUtils;
 
 /**
  * Fragment to manage the Contextual Search preference and to explain to the user what it does.
  */
-public class ContextualSearchPreferenceFragment extends PreferenceFragment {
-
+public class ContextualSearchPreferenceFragment extends PreferenceFragmentCompat {
     private static final String PREF_CONTEXTUAL_SEARCH_SWITCH = "contextual_search_switch";
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         PreferenceUtils.addPreferencesFromResource(this, R.xml.contextual_search_preferences);
         getActivity().setTitle(R.string.contextual_search_title);
         setHasOptionsMenu(true);
@@ -32,23 +28,19 @@
     }
 
     private void initContextualSearchSwitch() {
-        ChromeSwitchPreference contextualSearchSwitch =
-                (ChromeSwitchPreference) findPreference(PREF_CONTEXTUAL_SEARCH_SWITCH);
+        ChromeSwitchPreferenceCompat contextualSearchSwitch =
+                (ChromeSwitchPreferenceCompat) findPreference(PREF_CONTEXTUAL_SEARCH_SWITCH);
 
         boolean isContextualSearchEnabled =
                 !PrefServiceBridge.getInstance().isContextualSearchDisabled();
         contextualSearchSwitch.setChecked(isContextualSearchEnabled);
 
-        contextualSearchSwitch.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
-            @Override
-            public boolean onPreferenceChange(Preference preference, Object newValue) {
-                PrefServiceBridge.getInstance().setContextualSearchState((boolean) newValue);
-                ContextualSearchUma.logPreferenceChange((boolean) newValue);
-                return true;
-            }
+        contextualSearchSwitch.setOnPreferenceChangeListener((preference, newValue) -> {
+            PrefServiceBridge.getInstance().setContextualSearchState((boolean) newValue);
+            ContextualSearchUma.logPreferenceChange((boolean) newValue);
+            return true;
         });
         contextualSearchSwitch.setManagedPreferenceDelegate(
                 preference -> PrefServiceBridge.getInstance().isContextualSearchDisabledByPolicy());
     }
-
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchBoxDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchBoxDataProvider.java
index cde33f2..8d645c1e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchBoxDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchBoxDataProvider.java
@@ -123,4 +123,7 @@
     public @ColorRes int getSecurityIconColorStateList() {
         return 0;
     }
+
+    @Override
+    public void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo) {}
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesBridge.java
index 5276129..4e6de77 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesBridge.java
@@ -85,21 +85,25 @@
 
     @Override
     public void addBlacklistedUrl(String url) {
+        if (mNativeMostVisitedSitesBridge == 0) return;
         nativeAddOrRemoveBlacklistedUrl(mNativeMostVisitedSitesBridge, url, true);
     }
 
     @Override
     public void removeBlacklistedUrl(String url) {
+        if (mNativeMostVisitedSitesBridge == 0) return;
         nativeAddOrRemoveBlacklistedUrl(mNativeMostVisitedSitesBridge, url, false);
     }
 
     @Override
     public void recordPageImpression(int tilesCount) {
+        if (mNativeMostVisitedSitesBridge == 0) return;
         nativeRecordPageImpression(mNativeMostVisitedSitesBridge, tilesCount);
     }
 
     @Override
     public void recordTileImpression(Tile tile) {
+        if (mNativeMostVisitedSitesBridge == 0) return;
         nativeRecordTileImpression(mNativeMostVisitedSitesBridge, tile.getIndex(), tile.getType(),
                 tile.getIconType(), tile.getTitleSource(), tile.getSource(),
                 tile.getData().dataGenerationTime.getTime(), tile.getUrl());
@@ -107,6 +111,7 @@
 
     @Override
     public void recordOpenedMostVisitedItem(Tile tile) {
+        if (mNativeMostVisitedSitesBridge == 0) return;
         nativeRecordOpenedMostVisitedItem(mNativeMostVisitedSitesBridge, tile.getIndex(),
                 tile.getType(), tile.getTitleSource(), tile.getSource(),
                 tile.getData().dataGenerationTime.getTime());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabState.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabState.java
index 01fe3ca2..c787370 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabState.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabState.java
@@ -460,9 +460,10 @@
      * thread.
      * @param bundle Bundle to write the tab's state to.
      * @param state State object obtained from from {@link Tab#getState()}.
+     * @return Whether the tab state was successfully saved.
      */
-    public static void saveState(Bundle bundle, TabState state) {
-        if (state == null || state.contentsState == null) return;
+    public static boolean saveState(Bundle bundle, TabState state) {
+        if (state == null || state.contentsState == null) return false;
 
         byte[] contentsStateBytes = getContentStateByteArray(state.contentsState.buffer());
 
@@ -479,6 +480,7 @@
         bundle.putInt(VERSION, state.contentsState.version());
         bundle.putInt(THEME_COLOR, state.themeColor);
         bundle.putBoolean(IS_INCOGNITO, state.isIncognito());
+        return true;
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
index d94d1e8..e92934f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
@@ -47,11 +47,13 @@
     private final Context mContext;
 
     private Tab mTab;
-    private boolean mIsIncognito;
     private int mPrimaryColor;
+    private OverviewModeBehavior mOverviewModeBehavior;
+
+    private boolean mIsIncognito;
     private boolean mIsUsingBrandColor;
     private boolean mShouldShowOmniboxInOverviewMode;
-    private OverviewModeBehavior mOverviewModeBehavior;
+    private boolean mShouldShowGoogleLogo;
 
     private long mNativeLocationBarModelAndroid;
 
@@ -362,8 +364,17 @@
         // If we're showing a query in the omnibox, and the security level is high enough to show
         // the search icon, return that instead of the security icon.
         if (getDisplaySearchTerms() != null) {
-            return R.drawable.omnibox_search;
+            if (mShouldShowGoogleLogo) {
+                // TODO(crbug.com/973150): Fetch the favicon when the DSE isn't Google.
+                return R.drawable.ic_logo_googleg_24dp;
+            } else {
+                return R.drawable.omnibox_search;
+            }
+        } else if (getNewTabPageForCurrentTab() != null && mShouldShowGoogleLogo) {
+            // TODO(crbug.com/973150): Fetch the favicon when the DSE isn't Google.
+            return R.drawable.ic_logo_googleg_24dp;
         }
+
         return getSecurityIconResource(getSecurityLevel(), !isTablet, isOfflinePage(), isPreview());
     }
 
@@ -415,6 +426,12 @@
 
     @Override
     public @ColorRes int getSecurityIconColorStateList() {
+        // Don't apply tint to the search logo, which is shown on the NTP and the SRP pages.
+        if ((getNewTabPageForCurrentTab() != null || getDisplaySearchTerms() != null)
+                && mShouldShowGoogleLogo) {
+            return 0;
+        }
+
         int securityLevel = getSecurityLevel();
         int color = getPrimaryColor();
         boolean needLightIcon = ColorUtils.shouldUseLightForegroundOnBackground(color);
@@ -477,6 +494,11 @@
         return nativeGetURLForDisplay(mNativeLocationBarModelAndroid);
     }
 
+    @Override
+    public void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo) {
+        mShouldShowGoogleLogo = shouldShowGoogleLogo;
+    }
+
     private native long nativeInit();
     private native void nativeDestroy(long nativeLocationBarModelAndroid);
     private native String nativeGetFormattedFullURL(long nativeLocationBarModelAndroid);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java
index d4bcfa9..44e23ba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java
@@ -153,4 +153,9 @@
     default public String getDisplaySearchTerms() {
         return null;
     }
+
+    /**
+     * Turn on/off the google logo in the omnibox
+     */
+    void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 2593b38b..1e57a77 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -27,7 +27,6 @@
 import org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
-import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -114,7 +113,6 @@
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.NavigationEntry;
 import org.chromium.content_public.browser.NavigationHandle;
-import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.common.ContentUrlConstants;
 import org.chromium.ui.base.DeviceFormFactor;
@@ -1698,22 +1696,34 @@
      * @param activityName Simple class name for the activity this toolbar belongs to.
      */
     public void onDeferredStartup(final long activityCreationTimeMs, final String activityName) {
-        // Record startup performance statistics
+        recordStartupHistograms(activityCreationTimeMs, activityName);
+        mLocationBar.onDeferredStartup();
+    }
+
+    /**
+     * Record histograms covering Chrome startup.
+     * This method will collect metrics no sooner than RECORD_UMA_PERFORMANCE_METRICS_DELAY_MS since
+     * Activity creation to ensure availability of collected data.
+     *
+     * Histograms will not be collected if Chrome is destroyed before the above timeout passed.
+     */
+    private void recordStartupHistograms(
+            final long activityCreationTimeMs, final String activityName) {
+        // Schedule call to self if minimum time since activity creation has not yet passed.
         long elapsedTime = SystemClock.elapsedRealtime() - activityCreationTimeMs;
         if (elapsedTime < RECORD_UMA_PERFORMANCE_METRICS_DELAY_MS) {
-            PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT, () -> {
-                onDeferredStartup(activityCreationTimeMs, activityName);
-            }, RECORD_UMA_PERFORMANCE_METRICS_DELAY_MS - elapsedTime);
+            // clang-format off
+            mHandler.postDelayed(
+                    () -> recordStartupHistograms(activityCreationTimeMs, activityName),
+                    RECORD_UMA_PERFORMANCE_METRICS_DELAY_MS - elapsedTime);
+            // clang-format on
             return;
         }
+
         RecordHistogram.recordTimesHistogram("MobileStartup.ToolbarFirstDrawTime2." + activityName,
                 mToolbar.getFirstDrawTime() - activityCreationTimeMs);
 
-        // mOmniboxStartupMetrics might be null. ie. ToolbarManager is destroyed. See
-        // https://crbug.com/860449
-        if (mOmniboxStartupMetrics != null) mOmniboxStartupMetrics.maybeRecordHistograms();
-
-        mLocationBar.onDeferredStartup();
+        mOmniboxStartupMetrics.maybeRecordHistograms();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
index 47c94f9..c5dfdc5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
@@ -315,6 +315,9 @@
             public @ColorRes int getSecurityIconColorStateList() {
                 return 0;
             }
+
+            @Override
+            public void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo) {}
         };
 
         // Set menu button background in case it was previously called before inflation
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreInstallUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreInstallUtils.java
new file mode 100644
index 0000000..7394834
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreInstallUtils.java
@@ -0,0 +1,266 @@
+// 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.chrome.browser.vr;
+
+import android.app.Activity;
+
+import org.chromium.base.BundleUtils;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
+import org.chromium.chrome.browser.infobar.SimpleConfirmInfoBarBuilder;
+import org.chromium.chrome.browser.modules.ModuleInstallUi;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.components.module_installer.ModuleInstaller;
+
+/**
+ * Installs AR DFM and ArCore runtimes.
+ */
+@JNINamespace("vr")
+public class ArCoreInstallUtils implements ModuleInstallUi.FailureUiListener {
+    private static final String TAG = "ArCoreInstallUtils";
+
+    private long mNativeArCoreInstallUtils;
+
+    private Tab mTab;
+
+    // Instance that requested installation of ARCore.
+    // Should be non-null only if there is a pending request to install ARCore.
+    private static ArCoreInstallUtils sRequestInstallInstance;
+
+    // Cached ArCoreShim instance - valid only after AR module was installed and
+    // getArCoreShimInstance() was called.
+    private static ArCoreShim sArCoreInstance;
+
+    private static ArCoreShim getArCoreShimInstance() {
+        if (sArCoreInstance != null) return sArCoreInstance;
+
+        try {
+            sArCoreInstance =
+                    (ArCoreShim) Class.forName("org.chromium.chrome.browser.vr.ArCoreShimImpl")
+                            .newInstance();
+        } catch (ClassNotFoundException e) {
+            // shouldn't happen - we should only call this method once AR module is installed.
+            throw new RuntimeException(e);
+        } catch (InstantiationException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+
+        return sArCoreInstance;
+    }
+
+    private ArCoreInstallUtils(long nativeArCoreInstallUtils) {
+        mNativeArCoreInstallUtils = nativeArCoreInstallUtils;
+        // Need to be called before trying to access the AR module.
+        ModuleInstaller.getInstance().init();
+    }
+
+    @Override
+    public void onRetry() {
+        if (mNativeArCoreInstallUtils == 0) return;
+        requestInstallArModule(mTab);
+    }
+
+    @Override
+    public void onCancel() {
+        if (mNativeArCoreInstallUtils == 0) return;
+        ArCoreInstallUtilsJni.get().onRequestInstallArModuleResult(
+                mNativeArCoreInstallUtils, false);
+    }
+
+    @CalledByNative
+    private boolean canRequestInstallArModule() {
+        // We can only try to install the AR module if we are in a bundle mode.
+        return BundleUtils.isBundle();
+    }
+
+    @CalledByNative
+    private boolean shouldRequestInstallArModule() {
+        try {
+            // Try to find class in AR module that has not been obfuscated.
+            Class.forName("com.google.ar.core.ArCoreApk");
+            return false;
+        } catch (ClassNotFoundException e) {
+            return true;
+        }
+    }
+
+    @CalledByNative
+    private void requestInstallArModule(Tab tab) {
+        mTab = tab;
+        ModuleInstallUi ui = new ModuleInstallUi(mTab, R.string.ar_module_title, this);
+        ui.showInstallStartUi();
+        ModuleInstaller.getInstance().install("ar", success -> {
+            assert shouldRequestInstallArModule() != success;
+
+            if (success) {
+                // As per documentation, it's recommended to issue a call to
+                // ArCoreApk.checkAvailability() early in application lifecycle & ignore the result
+                // so that subsequent calls can return cached result:
+                // https://developers.google.com/ar/develop/java/enable-arcore
+                // This is as early in the app lifecycle as it gets for us - just after installing
+                // AR module.
+                getArCoreInstallStatus();
+            }
+
+            if (mNativeArCoreInstallUtils != 0) {
+                if (success) {
+                    ui.showInstallSuccessUi();
+                    ArCoreInstallUtilsJni.get().onRequestInstallArModuleResult(
+                            mNativeArCoreInstallUtils, success);
+                } else {
+                    ui.showInstallFailureUi();
+                    // early exit - user will be offered a choice to retry & install flow will
+                    // continue from onRetry / onCancel
+                    return;
+                }
+            }
+        });
+    }
+
+    private @ArCoreShim.Availability int getArCoreInstallStatus() {
+        return getArCoreShimInstance().checkAvailability(ContextUtils.getApplicationContext());
+    }
+
+    @CalledByNative
+    private boolean shouldRequestInstallSupportedArCore() {
+        @ArCoreShim.Availability
+        int availability = getArCoreInstallStatus();
+        // Skip ARCore installation if we are certain that it is already installed.
+        // In all other cases, we might as well try to install it and handle installation failures.
+        return availability != ArCoreShim.Availability.SUPPORTED_INSTALLED;
+    }
+
+    @CalledByNative
+    private void requestInstallSupportedArCore(final Tab tab) {
+        assert shouldRequestInstallSupportedArCore();
+
+        @ArCoreShim.Availability
+        int arCoreAvailability = getArCoreInstallStatus();
+        final Activity activity = tab.getActivity();
+        String infobarText = null;
+        String buttonText = null;
+        switch (arCoreAvailability) {
+            case ArCoreShim.Availability.UNSUPPORTED_DEVICE_NOT_CAPABLE:
+                maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
+                break;
+            case ArCoreShim.Availability.UNKNOWN_CHECKING:
+            case ArCoreShim.Availability.UNKNOWN_ERROR:
+            case ArCoreShim.Availability.UNKNOWN_TIMED_OUT:
+            case ArCoreShim.Availability.SUPPORTED_NOT_INSTALLED:
+                infobarText = activity.getString(R.string.ar_core_check_infobar_install_text);
+                buttonText = activity.getString(R.string.app_banner_install);
+                break;
+            case ArCoreShim.Availability.SUPPORTED_APK_TOO_OLD:
+                infobarText = activity.getString(R.string.ar_core_check_infobar_update_text);
+                buttonText = activity.getString(R.string.update_from_market);
+                break;
+            case ArCoreShim.Availability.SUPPORTED_INSTALLED:
+                assert false;
+                break;
+        }
+
+        SimpleConfirmInfoBarBuilder.Listener listener = new SimpleConfirmInfoBarBuilder.Listener() {
+            @Override
+            public void onInfoBarDismissed() {
+                maybeNotifyNativeOnRequestInstallSupportedArCoreResult(
+                        !shouldRequestInstallSupportedArCore());
+            }
+
+            @Override
+            public boolean onInfoBarButtonClicked(boolean isPrimary) {
+                try {
+                    assert sRequestInstallInstance == null;
+                    @ArCoreShim.InstallStatus
+                    int installStatus = getArCoreShimInstance().requestInstall(activity, true);
+
+                    if (installStatus == ArCoreShim.InstallStatus.INSTALL_REQUESTED) {
+                        // Install flow will resume in onArCoreRequestInstallReturned, mark that
+                        // there is active request. Native code notification will be deferred until
+                        // our activity gets resumed.
+                        sRequestInstallInstance = ArCoreInstallUtils.this;
+                    } else if (installStatus == ArCoreShim.InstallStatus.INSTALLED) {
+                        // No need to install - notify native code.
+                        maybeNotifyNativeOnRequestInstallSupportedArCoreResult(true);
+                    }
+
+                } catch (ArCoreShim.UnavailableDeviceNotCompatibleException e) {
+                    sRequestInstallInstance = null;
+                    Log.w(TAG, "ARCore installation request failed with exception: %s",
+                            e.toString());
+
+                    maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
+                } catch (ArCoreShim.UnavailableUserDeclinedInstallationException e) {
+                    maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
+                }
+
+                return false;
+            }
+
+            @Override
+            public boolean onInfoBarLinkClicked() {
+                return false;
+            }
+        };
+        // TODO(ijamardo, https://crbug.com/838833): Add icon for AR info bar.
+        SimpleConfirmInfoBarBuilder.create(tab, listener, InfoBarIdentifier.AR_CORE_UPGRADE_ANDROID,
+                R.drawable.ic_error_outline_googblue_24dp, infobarText, buttonText, null, null,
+                true);
+    }
+
+    /**
+     * Helper used to notify native code about the result of the request to install ARCore.
+     */
+    private void maybeNotifyNativeOnRequestInstallSupportedArCoreResult(boolean success) {
+        if (mNativeArCoreInstallUtils != 0) {
+            ArCoreInstallUtilsJni.get().onRequestInstallSupportedArCoreResult(
+                    mNativeArCoreInstallUtils, success);
+        }
+    }
+
+    private void onArCoreRequestInstallReturned(Activity activity) {
+        try {
+            // Since |userRequestedInstall| parameter is false, the below call should
+            // throw if ARCore is still not installed - no need to check the result.
+            getArCoreShimInstance().requestInstall(activity, false);
+            maybeNotifyNativeOnRequestInstallSupportedArCoreResult(true);
+        } catch (ArCoreShim.UnavailableDeviceNotCompatibleException e) {
+            Log.w(TAG, "Exception thrown when trying to validate install state of ARCore: %s",
+                    e.toString());
+            maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
+        } catch (ArCoreShim.UnavailableUserDeclinedInstallationException e) {
+            maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
+        }
+    }
+
+    /**
+     * This method should be called by the Activity that gets resumed.
+     * We are only interested in the cases where our current Activity got paused
+     * as a result of a call to ArCoreApk.requestInstall() method.
+     */
+    public static void onResumeActivityWithNative(Activity activity) {
+        if (sRequestInstallInstance != null) {
+            sRequestInstallInstance.onArCoreRequestInstallReturned(activity);
+            sRequestInstallInstance = null;
+        }
+    }
+
+    public static void installArCoreDeviceProviderFactory() {
+        ArCoreInstallUtilsJni.get().installArCoreDeviceProviderFactory();
+    }
+
+    @NativeMethods
+    /* package */ interface ArConsentPromptNative {
+        void onRequestInstallArModuleResult(long nativeArCoreConsentPrompt, boolean success);
+        void onRequestInstallSupportedArCoreResult(long nativeArCoreConsentPrompt, boolean success);
+        void installArCoreDeviceProviderFactory();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java
index ad81f10..8b8dc7c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java
@@ -4,81 +4,31 @@
 
 package org.chromium.chrome.browser.vr;
 
-import android.app.Activity;
 import android.content.Context;
 import android.view.Surface;
 
 import dalvik.system.BaseDexClassLoader;
 
-import org.chromium.base.BundleUtils;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.StrictModeContext;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
-import org.chromium.chrome.browser.infobar.SimpleConfirmInfoBarBuilder;
-import org.chromium.chrome.browser.modules.ModuleInstallUi;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.components.module_installer.ModuleInstaller;
 
 /**
  * Provides ARCore classes access to java-related app functionality.
  */
 @JNINamespace("vr")
-public class ArCoreJavaUtils implements ModuleInstallUi.FailureUiListener {
+public class ArCoreJavaUtils {
     private static final String TAG = "ArCoreJavaUtils";
     private static final boolean DEBUG_LOGS = false;
 
     private long mNativeArCoreJavaUtils;
-    private boolean mAppInfoInitialized;
-    private Tab mTab;
 
     private ArImmersiveOverlay mArImmersiveOverlay;
 
-    // Instance that requested installation of ARCore.
-    // Should be non-null only if there is a pending request to install ARCore.
-    private static ArCoreJavaUtils sRequestInstallInstance;
-
-    // Cached ArCoreShim instance - valid only after AR module was installed and
-    // getArCoreShimInstance() was called.
-    private static ArCoreShim sArCoreInstance;
-
-    private static ArCoreShim getArCoreShimInstance() {
-        if (sArCoreInstance != null) return sArCoreInstance;
-
-        try {
-            sArCoreInstance =
-                    (ArCoreShim) Class.forName("org.chromium.chrome.browser.vr.ArCoreShimImpl")
-                            .newInstance();
-        } catch (ClassNotFoundException e) {
-            // shouldn't happen - we should only call this method once AR module is installed.
-            throw new RuntimeException(e);
-        } catch (InstantiationException e) {
-            throw new RuntimeException(e);
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException(e);
-        }
-
-        return sArCoreInstance;
-    }
-
-    public static void installArCoreDeviceProviderFactory() {
-        nativeInstallArCoreDeviceProviderFactory();
-    }
-
-    /**
-     * Gets the current application context.
-     *
-     * @return Context The application context.
-     */
-    @CalledByNative
-    private static Context getApplicationContext() {
-        return ContextUtils.getApplicationContext();
-    }
-
     @CalledByNative
     private static ArCoreJavaUtils create(long nativeArCoreJavaUtils) {
         ThreadUtils.assertOnUiThread();
@@ -93,22 +43,19 @@
         }
     }
 
-    @Override
-    public void onRetry() {
-        if (mNativeArCoreJavaUtils == 0) return;
-        requestInstallArModule(mTab);
-    }
-
-    @Override
-    public void onCancel() {
-        if (mNativeArCoreJavaUtils == 0) return;
-        nativeOnRequestInstallArModuleResult(mNativeArCoreJavaUtils, /* success = */ false);
+    /**
+     * Gets the current application context.
+     *
+     * @return Context The application context.
+     */
+    @CalledByNative
+    private static Context getApplicationContext() {
+        return ContextUtils.getApplicationContext();
     }
 
     private ArCoreJavaUtils(long nativeArCoreJavaUtils) {
         if (DEBUG_LOGS) Log.i(TAG, "constructor, nativeArCoreJavaUtils=" + nativeArCoreJavaUtils);
         mNativeArCoreJavaUtils = nativeArCoreJavaUtils;
-        initializeAppInfo();
     }
 
     @CalledByNative
@@ -151,194 +98,6 @@
         mNativeArCoreJavaUtils = 0;
     }
 
-    private void initializeAppInfo() {
-        mAppInfoInitialized = true;
-
-        // Need to be called before trying to access the AR module.
-        ModuleInstaller.init();
-    }
-
-    private @ArCoreShim.Availability int getArCoreInstallStatus() {
-        return getArCoreShimInstance().checkAvailability(getApplicationContext());
-    }
-
-    @CalledByNative
-    private boolean shouldRequestInstallSupportedArCore() {
-        @ArCoreShim.Availability
-        int availability = getArCoreInstallStatus();
-        // Skip ARCore installation if we are certain that it is already installed.
-        // In all other cases, we might as well try to install it and handle installation failures.
-        return availability != ArCoreShim.Availability.SUPPORTED_INSTALLED;
-    }
-
-    @CalledByNative
-    private void requestInstallArModule(Tab tab) {
-        mTab = tab;
-        ModuleInstallUi ui = new ModuleInstallUi(mTab, R.string.ar_module_title, this);
-        ui.showInstallStartUi();
-        ModuleInstaller.install("ar", success -> {
-            assert shouldRequestInstallArModule() != success;
-
-            if (success) {
-                // As per documentation, it's recommended to issue a call to
-                // ArCoreApk.checkAvailability() early in application lifecycle & ignore the result
-                // so that subsequent calls can return cached result:
-                // https://developers.google.com/ar/develop/java/enable-arcore
-                // This is as early in the app lifecycle as it gets for us - just after installing
-                // AR module.
-                getArCoreInstallStatus();
-            }
-
-            if (mNativeArCoreJavaUtils != 0) {
-                if (success) {
-                    ui.showInstallSuccessUi();
-                    nativeOnRequestInstallArModuleResult(mNativeArCoreJavaUtils, success);
-                } else {
-                    ui.showInstallFailureUi();
-                    // early exit - user will be offered a choice to retry & install flow will
-                    // continue from onRetry / onCancel
-                    return;
-                }
-            }
-        });
-    }
-
-    @CalledByNative
-    private void requestInstallSupportedArCore(final Tab tab) {
-        assert shouldRequestInstallSupportedArCore();
-
-        @ArCoreShim.Availability
-        int arCoreAvailability = getArCoreInstallStatus();
-        final Activity activity = tab.getActivity();
-        String infobarText = null;
-        String buttonText = null;
-        switch (arCoreAvailability) {
-            case ArCoreShim.Availability.UNSUPPORTED_DEVICE_NOT_CAPABLE:
-                maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
-                break;
-            case ArCoreShim.Availability.UNKNOWN_CHECKING:
-            case ArCoreShim.Availability.UNKNOWN_ERROR:
-            case ArCoreShim.Availability.UNKNOWN_TIMED_OUT:
-            case ArCoreShim.Availability.SUPPORTED_NOT_INSTALLED:
-                infobarText = activity.getString(R.string.ar_core_check_infobar_install_text);
-                buttonText = activity.getString(R.string.app_banner_install);
-                break;
-            case ArCoreShim.Availability.SUPPORTED_APK_TOO_OLD:
-                infobarText = activity.getString(R.string.ar_core_check_infobar_update_text);
-                buttonText = activity.getString(R.string.update_from_market);
-                break;
-            case ArCoreShim.Availability.SUPPORTED_INSTALLED:
-                assert false;
-                break;
-        }
-
-        SimpleConfirmInfoBarBuilder.Listener listener = new SimpleConfirmInfoBarBuilder.Listener() {
-            @Override
-            public void onInfoBarDismissed() {
-                maybeNotifyNativeOnRequestInstallSupportedArCoreResult(
-                        !shouldRequestInstallSupportedArCore());
-            }
-
-            @Override
-            public boolean onInfoBarButtonClicked(boolean isPrimary) {
-                try {
-                    assert sRequestInstallInstance == null;
-                    @ArCoreShim.InstallStatus
-                    int installStatus = getArCoreShimInstance().requestInstall(activity, true);
-
-                    if (installStatus == ArCoreShim.InstallStatus.INSTALL_REQUESTED) {
-                        // Install flow will resume in onArCoreRequestInstallReturned, mark that
-                        // there is active request. Native code notification will be deferred until
-                        // our activity gets resumed.
-                        sRequestInstallInstance = ArCoreJavaUtils.this;
-                    } else if (installStatus == ArCoreShim.InstallStatus.INSTALLED) {
-                        // No need to install - notify native code.
-                        maybeNotifyNativeOnRequestInstallSupportedArCoreResult(true);
-                    }
-
-                } catch (ArCoreShim.UnavailableDeviceNotCompatibleException e) {
-                    sRequestInstallInstance = null;
-                    Log.w(TAG, "ARCore installation request failed with exception: %s",
-                            e.toString());
-
-                    maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
-                } catch (ArCoreShim.UnavailableUserDeclinedInstallationException e) {
-                    maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
-                }
-
-                return false;
-            }
-
-            @Override
-            public boolean onInfoBarLinkClicked() {
-                return false;
-            }
-        };
-        // TODO(ijamardo, https://crbug.com/838833): Add icon for AR info bar.
-        SimpleConfirmInfoBarBuilder.create(tab, listener, InfoBarIdentifier.AR_CORE_UPGRADE_ANDROID,
-                R.drawable.ic_error_outline_googblue_24dp, infobarText, buttonText, null, null,
-                true);
-    }
-
-    @CalledByNative
-    private boolean canRequestInstallArModule() {
-        // We can only try to install the AR module if we are in a bundle mode.
-        return BundleUtils.isBundle();
-    }
-
-    @CalledByNative
-    private boolean shouldRequestInstallArModule() {
-        try {
-            // Try to find class in AR module that has not been obfuscated.
-            Class.forName("com.google.ar.core.ArCoreApk");
-            return false;
-        } catch (ClassNotFoundException e) {
-            return true;
-        }
-    }
-
-    private void onArCoreRequestInstallReturned(Activity activity) {
-        try {
-            // Since |userRequestedInstall| parameter is false, the below call should
-            // throw if ARCore is still not installed - no need to check the result.
-            getArCoreShimInstance().requestInstall(activity, false);
-            maybeNotifyNativeOnRequestInstallSupportedArCoreResult(true);
-        } catch (ArCoreShim.UnavailableDeviceNotCompatibleException e) {
-            Log.w(TAG, "Exception thrown when trying to validate install state of ARCore: %s",
-                    e.toString());
-            maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
-        } catch (ArCoreShim.UnavailableUserDeclinedInstallationException e) {
-            maybeNotifyNativeOnRequestInstallSupportedArCoreResult(false);
-        }
-    }
-
-    /**
-     * This method should be called by the Activity that gets resumed.
-     * We are only interested in the cases where our current Activity got paused
-     * as a result of a call to ArCoreApk.requestInstall() method.
-     */
-    public static void onResumeActivityWithNative(Activity activity) {
-        if (sRequestInstallInstance != null) {
-            sRequestInstallInstance.onArCoreRequestInstallReturned(activity);
-            sRequestInstallInstance = null;
-        }
-    }
-
-    /**
-     * Helper used to notify native code about the result of the request to install ARCore.
-     */
-    private void maybeNotifyNativeOnRequestInstallSupportedArCoreResult(boolean success) {
-        if (mNativeArCoreJavaUtils != 0) {
-            nativeOnRequestInstallSupportedArCoreResult(mNativeArCoreJavaUtils, success);
-        }
-    }
-
-    private static native void nativeInstallArCoreDeviceProviderFactory();
-    private native void nativeOnRequestInstallArModuleResult(
-            long nativeArCoreJavaUtils, boolean success);
-    private native void nativeOnRequestInstallSupportedArCoreResult(
-            long nativeArCoreJavaUtils, boolean success);
-
     private native void nativeOnDrawingSurfaceReady(
             long nativeArCoreJavaUtils, Surface surface, int rotation, int width, int height);
     private native void nativeOnDrawingSurfaceTouch(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArDelegateImpl.java
index 9fc202e..500cb1c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArDelegateImpl.java
@@ -19,11 +19,11 @@
 
     @Override
     public void init() {
-        ArCoreJavaUtils.installArCoreDeviceProviderFactory();
+        ArCoreInstallUtils.installArCoreDeviceProviderFactory();
     }
 
     @Override
     public void registerOnResumeActivity(Activity activity) {
-        ArCoreJavaUtils.onResumeActivityWithNative(activity);
+        ArCoreInstallUtils.onResumeActivityWithNative(activity);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
index d66d815..d127500 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
@@ -110,6 +110,9 @@
         @WebApkUpdateReason
         int updateReason = needsUpdate(mInfo, fetchedInfo, primaryIconUrl, badgeIconUrl);
         boolean needsUpgrade = (updateReason != WebApkUpdateReason.NONE);
+        if (mStorage.shouldForceUpdate() && needsUpgrade) {
+            updateReason = WebApkUpdateReason.MANUALLY_TRIGGERED;
+        }
         Log.i(TAG, "Got Manifest: " + gotManifest);
         Log.i(TAG, "WebAPK upgrade needed: " + needsUpgrade);
 
@@ -130,7 +133,7 @@
         }
 
         if (!needsUpgrade) {
-            if (!mStorage.didPreviousUpdateSucceed()) {
+            if (!mStorage.didPreviousUpdateSucceed() || mStorage.shouldForceUpdate()) {
                 onFinishedUpdate(mStorage, WebApkInstallResult.SUCCESS, false /* relaxUpdates */);
             }
             return;
@@ -178,18 +181,30 @@
     /** Schedules update for when WebAPK is not running. */
     private void scheduleUpdate() {
         WebApkUma.recordUpdateRequestQueued(1);
+        TaskInfo updateTask;
+        if (mStorage.shouldForceUpdate()) {
+            // Start an update task ASAP for forced updates.
+            updateTask = TaskInfo.createOneOffTask(TaskIds.WEBAPK_UPDATE_JOB_ID,
+                                         WebApkUpdateTask.class, 0 /* windowEndTimeMs */)
+                                 .setUpdateCurrent(true)
+                                 .setIsPersisted(true)
+                                 .build();
+            mStorage.setUpdateScheduled(true);
+            mStorage.setShouldForceUpdate(false);
+        } else {
+            // The task deadline should be before {@link WebappDataStorage#RETRY_UPDATE_DURATION}
+            updateTask =
+                    TaskInfo.createOneOffTask(TaskIds.WEBAPK_UPDATE_JOB_ID, WebApkUpdateTask.class,
+                                    DateUtils.HOUR_IN_MILLIS, DateUtils.HOUR_IN_MILLIS * 23)
+                            .setRequiredNetworkType(TaskInfo.NetworkType.UNMETERED)
+                            .setUpdateCurrent(true)
+                            .setIsPersisted(true)
+                            .setRequiresCharging(true)
+                            .build();
+        }
 
-        // The task deadline should be before {@link WebappDataStorage#RETRY_UPDATE_DURATION}
-        TaskInfo taskInfo =
-                TaskInfo.createOneOffTask(TaskIds.WEBAPK_UPDATE_JOB_ID, WebApkUpdateTask.class,
-                                DateUtils.HOUR_IN_MILLIS, DateUtils.HOUR_IN_MILLIS * 23)
-                        .setRequiredNetworkType(TaskInfo.NetworkType.UNMETERED)
-                        .setUpdateCurrent(true)
-                        .setIsPersisted(true)
-                        .setRequiresCharging(true)
-                        .build();
         BackgroundTaskSchedulerFactory.getScheduler().schedule(
-                ContextUtils.getApplicationContext(), taskInfo);
+                ContextUtils.getApplicationContext(), updateTask);
     }
 
     /** Sends update request to the WebAPK Server. Should be called when WebAPK is not running. */
@@ -284,6 +299,8 @@
      */
     private static void onFinishedUpdate(
             WebappDataStorage storage, @WebApkInstallResult int result, boolean relaxUpdates) {
+        storage.setShouldForceUpdate(false);
+        storage.setUpdateScheduled(false);
         recordUpdate(storage, result, relaxUpdates);
         storage.deletePendingUpdateRequestFile();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateTask.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateTask.java
index 76d62c8..2a18102 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateTask.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateTask.java
@@ -4,17 +4,15 @@
 
 package org.chromium.chrome.browser.webapps;
 
-import android.app.Activity;
 import android.content.Context;
-import android.text.TextUtils;
 
-import org.chromium.base.ApplicationStatus;
 import org.chromium.base.StrictModeContext;
 import org.chromium.chrome.browser.background_task_scheduler.NativeBackgroundTask;
 import org.chromium.components.background_task_scheduler.BackgroundTask.TaskFinishedCallback;
 import org.chromium.components.background_task_scheduler.TaskIds;
 import org.chromium.components.background_task_scheduler.TaskParameters;
 
+import java.lang.ref.WeakReference;
 import java.util.List;
 
 /**
@@ -41,7 +39,9 @@
         List<String> ids = WebappRegistry.getInstance().findWebApksWithPendingUpdate();
         for (String id : ids) {
             WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(id);
-            if (!isWebApkActivityRunning(storage.getWebApkPackageName())) {
+            WeakReference<WebappActivity> activity =
+                    WebappActivity.findRunningWebappActivityWithId(storage.getId());
+            if (activity == null || activity.get() == null) {
                 mStorageToUpdate = storage;
                 mMoreToUpdate = ids.size() > 1;
                 return StartBeforeNativeResult.LOAD_NATIVE;
@@ -79,19 +79,4 @@
 
     @Override
     public void reschedule(Context context) {}
-
-    /** Returns whether a WebApkActivity with {@link webApkPackageName} is running. */
-    private static boolean isWebApkActivityRunning(String webApkPackageName) {
-        for (Activity activity : ApplicationStatus.getRunningActivities()) {
-            if (!(activity instanceof WebApkActivity)) {
-                continue;
-            }
-            WebApkActivity webApkActivity = (WebApkActivity) activity;
-            if (webApkActivity != null
-                    && TextUtils.equals(webApkPackageName, webApkActivity.getWebApkPackageName())) {
-                return true;
-            }
-        }
-        return false;
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
index 09a30313..abfe271 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
@@ -127,6 +127,21 @@
         return null;
     }
 
+    /** Returns the WebappActivity with the given {@link webappId}. */
+    public static WeakReference<WebappActivity> findRunningWebappActivityWithId(String webappId) {
+        for (Activity activity : ApplicationStatus.getRunningActivities()) {
+            if (!(activity instanceof WebappActivity)) {
+                continue;
+            }
+            WebappActivity webappActivity = (WebappActivity) activity;
+            if (webappActivity != null
+                    && TextUtils.equals(webappId, webappActivity.getWebappInfo().id())) {
+                return new WeakReference<>(webappActivity);
+            }
+        }
+        return null;
+    }
+
     /**
      * Construct all the variables that shouldn't change.  We do it here both to clarify when the
      * objects are created and to ensure that they exist throughout the parallelized initialization
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
index b95b84c..3228a61 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
@@ -77,6 +77,12 @@
     // The path where serialized update data is written before uploading to the WebAPK server.
     static final String KEY_PENDING_UPDATE_FILE_PATH = "pending_update_file_path";
 
+    // Whether to force an update.
+    static final String KEY_SHOULD_FORCE_UPDATE = "should_force_update";
+
+    // Whether an update has been scheduled.
+    static final String KEY_UPDATE_SCHEDULED = "update_scheduled";
+
     // Number of milliseconds between checks for whether the WebAPK's Web Manifest has changed.
     public static final long UPDATE_INTERVAL = DateUtils.DAY_IN_MILLIS;
 
@@ -514,6 +520,33 @@
         return mPreferences.getBoolean(KEY_RELAX_UPDATES, false);
     }
 
+    /** Sets whether an update has been scheduled. */
+    public void setUpdateScheduled(boolean isUpdateScheduled) {
+        mPreferences.edit().putBoolean(KEY_UPDATE_SCHEDULED, isUpdateScheduled).apply();
+    }
+
+    /** Gets whether an update has been scheduled. */
+    public boolean isUpdateScheduled() {
+        return mPreferences.getBoolean(KEY_UPDATE_SCHEDULED, false);
+    }
+
+    /** Sets whether an update should be forced. */
+    public void setShouldForceUpdate(boolean forceUpdate) {
+        mPreferences.edit().putBoolean(KEY_SHOULD_FORCE_UPDATE, forceUpdate).apply();
+    }
+
+    /** Whether to force an update. */
+    public boolean shouldForceUpdate() {
+        return mPreferences.getBoolean(KEY_SHOULD_FORCE_UPDATE, false);
+    }
+
+    /** Returns the update status. */
+    public String getUpdateStatus() {
+        if (isUpdateScheduled()) return "Scheduled";
+        if (shouldForceUpdate()) return "Pending";
+        return didPreviousUpdateSucceed() ? "Succeeded" : "Failed";
+    }
+
     /**
      * Returns file where WebAPK update data should be stored and stores the file name in
      * SharedPreferences.
@@ -556,6 +589,7 @@
 
     /** Returns whether we should check for update. */
     boolean shouldCheckForUpdate() {
+        if (shouldForceUpdate()) return true;
         long checkUpdatesInterval =
                 shouldRelaxUpdates() ? RELAXED_UPDATE_INTERVAL : UPDATE_INTERVAL;
         long now = sClock.currentTimeMillis();
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index cadb698..0802da7 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -1147,7 +1147,7 @@
 
       <!-- Single site settings -->
       <message name="IDS_NO_SAVED_WEBSITE_SETTINGS" desc="Text to display when there are no saved website settings.">
-        You have no saved website settings.
+        No storage data here
       </message>
 
       <!-- Languages preferences -->
@@ -1814,7 +1814,7 @@
 
       <!-- Bluetooth Scanning Prompt strings -->
       <message name="IDS_BLUETOOTH_SCANNING_PROMPT_ORIGIN" desc="The label that is used to introduce Bluetooth scanning prompt details to the user when it is from a website.">
-        <ph name="SITE">%1$s<ex>www.google.com</ex></ph> wants to scan for nearby Bluetooth devices, the following devices have been found:
+        <ph name="SITE">%1$s<ex>www.google.com</ex></ph> wants to scan for nearby Bluetooth devices. The following devices have been found:
       </message>
       <message name="IDS_BLUETOOTH_SCANNING_DEVICE_UNKNOWN" desc="Text to identify Bluetooth devices of unknown or unsupported class.">
         Unknown or unsupported device (<ph name="DEVICE_ID">%1$s<ex>A1:B2:C3:D4:E5:F6</ex></ph>)
@@ -2248,7 +2248,7 @@
 
       <!-- Page info popup -->
       <message name="IDS_PAGE_INFO_SITE_SETTINGS_BUTTON" desc="Text in the button that opens a website's Site Settings from the Page Info dialog.">
-        Site Settings
+        Site settings
       </message>
       <message name="IDS_PAGE_INFO_INSTANT_APP_BUTTON" desc="Text in the button that opens an Android Instant app that is associated with the website's URL.">
         Open Instant App
diff --git a/chrome/android/java/strings/android_chrome_strings_grd/IDS_NO_SAVED_WEBSITE_SETTINGS.png.sha1 b/chrome/android/java/strings/android_chrome_strings_grd/IDS_NO_SAVED_WEBSITE_SETTINGS.png.sha1
new file mode 100644
index 0000000..4d3c5db
--- /dev/null
+++ b/chrome/android/java/strings/android_chrome_strings_grd/IDS_NO_SAVED_WEBSITE_SETTINGS.png.sha1
@@ -0,0 +1 @@
+61c67160243523857d934b35eb74acf104e05675
\ No newline at end of file
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index a1b751d..a860903c6 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -44,6 +44,7 @@
 
 if (enable_arcore) {
   chrome_java_sources += [
+    "java/src/org/chromium/chrome/browser/vr/ArCoreInstallUtils.java",
     "java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java",
     "java/src/org/chromium/chrome/browser/vr/ArDelegateImpl.java",
     "java/src/org/chromium/chrome/browser/vr/ArCoreShim.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewTest.java
index 41879a7..00f0b40 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuHeaderViewTest.java
@@ -43,6 +43,7 @@
     private View mHeaderView;
     private TextView mTitle;
     private TextView mUrl;
+    private View mTitleAndUrl;
     private RoundedCornerImageView mImage;
     private View mCircleBg;
     private PropertyModel mModel;
@@ -61,6 +62,7 @@
             mHeaderView = getActivity().findViewById(android.R.id.content);
             mTitle = mHeaderView.findViewById(R.id.menu_header_title);
             mUrl = mHeaderView.findViewById(R.id.menu_header_url);
+            mTitleAndUrl = mHeaderView.findViewById(R.id.title_and_url);
             mImage = mHeaderView.findViewById(R.id.menu_header_image);
             mCircleBg = mHeaderView.findViewById(R.id.circle_background);
         });
@@ -131,8 +133,10 @@
     @SmallTest
     @UiThreadTest
     public void testUrlClick() {
+        // Even though the click event expands/shrinks the url, the click target is the LinearLayout
+        // that contains the title and the url to give the user more area to touch.
         assertFalse("URL has onClickListeners when it shouldn't, yet, have.",
-                mUrl.hasOnClickListeners());
+                mTitleAndUrl.hasOnClickListeners());
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mModel.set(RevampedContextMenuHeaderProperties.URL, URL_STRING);
@@ -146,14 +150,14 @@
                             RevampedContextMenuHeaderProperties.URL_MAX_LINES, Integer.MAX_VALUE);
                 }
             });
-            mUrl.callOnClick();
+            mTitleAndUrl.callOnClick();
         });
 
         assertThat("Incorrect max line count for URL.", mUrl.getMaxLines(),
                 equalTo(Integer.MAX_VALUE));
         assertNull("URL is ellipsized when it shouldn't be.", mUrl.getEllipsize());
 
-        TestThreadUtils.runOnUiThreadBlocking(() -> { mUrl.callOnClick(); });
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mTitleAndUrl.callOnClick(); });
 
         assertThat("Incorrect max line count for URL.", mUrl.getMaxLines(), equalTo(URL_MAX_COUNT));
         assertThat("Incorrect URL ellipsize mode.", mUrl.getEllipsize(),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothChooserDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialogTest.java
similarity index 98%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothChooserDialogTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialogTest.java
index 2904da0..6d59149 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothChooserDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothChooserDialogTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.Manifest;
 import android.app.Dialog;
@@ -24,6 +24,8 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.components.location.LocationUtils;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialogTest.java
similarity index 96%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialogTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialogTest.java
index 5d431fc..f984d86 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/BluetoothScanningPermissionDialogTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.app.Dialog;
 import android.support.test.filters.LargeTest;
@@ -20,6 +20,8 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.components.security_state.ConnectionSecurityLevel;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ItemChooserDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialogTest.java
similarity index 99%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/ItemChooserDialogTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialogTest.java
index 6dafc3a..c5986a6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ItemChooserDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/ItemChooserDialogTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.app.Dialog;
 import android.graphics.drawable.Drawable;
@@ -26,6 +26,8 @@
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.test.util.Criteria;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/OWNERS
new file mode 100644
index 0000000..19c774f
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/OWNERS
@@ -0,0 +1 @@
+file://chrome/android/java/src/org/chromium/chrome/browser/device_dialog/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/UsbChooserDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialogTest.java
similarity index 97%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/UsbChooserDialogTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialogTest.java
index 69b47c9..4f40fb4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/UsbChooserDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/device_dialog/UsbChooserDialogTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser;
+package org.chromium.chrome.browser.device_dialog;
 
 import android.app.Dialog;
 import android.support.test.filters.LargeTest;
@@ -19,6 +19,8 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.components.security_state.ConnectionSecurityLevel;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java
index 26714cd..a318f90 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java
@@ -257,6 +257,9 @@
         public @ColorRes int getSecurityIconColorStateList() {
             return 0;
         }
+
+        @Override
+        public void setShouldShowGoogleLogo(boolean shouldShowGoogleLogo) {}
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java
index 3036629..127ae5b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java
@@ -94,6 +94,10 @@
 
     // PaymentAppCreatedCallback
     @Override
+    public void onGetPaymentAppsError(String errorMessage) {}
+
+    // PaymentAppCreatedCallback
+    @Override
     public void onAllPaymentAppsCreated() {
         mAllPaymentAppsCreated = true;
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java
index 0b8aadd..cb687dd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java
@@ -62,6 +62,7 @@
     private boolean mDownloadPaymentMethodManifestSuccess;
     private boolean mDownloadWebAppManifestSuccess;
     private boolean mDownloadFailure;
+    private String mErrorMessage;
     private String mPaymentMethodManifest;
     private String mWebAppManifest;
 
@@ -80,9 +81,10 @@
     }
 
     @Override
-    public void onManifestDownloadFailure() {
+    public void onManifestDownloadFailure(String errorMessage) {
         mDownloadComplete = true;
         mDownloadFailure = true;
+        mErrorMessage = errorMessage;
     }
 
     @Before
@@ -96,6 +98,7 @@
         mDownloadPaymentMethodManifestSuccess = false;
         mDownloadWebAppManifestSuccess = false;
         mDownloadFailure = false;
+        mErrorMessage = "";
         mPaymentMethodManifest = null;
         mWebAppManifest = null;
     }
@@ -139,6 +142,8 @@
         });
 
         Assert.assertTrue("Web app manifest should not have been downloaded.", mDownloadFailure);
+        Assert.assertEquals(
+                "Unable to download payment manifest \"" + uri.toString() + "\".", mErrorMessage);
     }
 
     @Test
@@ -174,6 +179,9 @@
 
         Assert.assertTrue(
                 "Payment method manifest should have not have been downloaded.", mDownloadFailure);
+        Assert.assertEquals("Unable to make a HEAD request to \"" + uri.toString()
+                        + "\" for payment method manifest.",
+                mErrorMessage);
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferencesTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferencesTest.java
index 9535cf9..558e913 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferencesTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/autofill_assistant/AutofillAssistantPreferencesTest.java
@@ -23,7 +23,7 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.history.HistoryActivity;
-import org.chromium.chrome.browser.preferences.ChromeSwitchPreference;
+import org.chromium.chrome.browser.preferences.ChromeSwitchPreferenceCompat;
 import org.chromium.chrome.browser.preferences.MainPreferences;
 import org.chromium.chrome.browser.preferences.Preferences;
 import org.chromium.chrome.browser.preferences.PreferencesTest;
@@ -85,15 +85,15 @@
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             AutofillAssistantPreferences autofillAssistantPrefs =
-                    (AutofillAssistantPreferences) preferences.getMainFragment();
-            ChromeSwitchPreference onOffSwitch =
-                    (ChromeSwitchPreference) autofillAssistantPrefs.findPreference(
+                    (AutofillAssistantPreferences) preferences.getMainFragmentCompat();
+            ChromeSwitchPreferenceCompat onOffSwitch =
+                    (ChromeSwitchPreferenceCompat) autofillAssistantPrefs.findPreference(
                             AutofillAssistantPreferences.PREF_AUTOFILL_ASSISTANT_SWITCH);
             Assert.assertTrue(onOffSwitch.isChecked());
 
-            PreferencesTest.clickPreference(autofillAssistantPrefs, onOffSwitch);
+            onOffSwitch.performClick();
             Assert.assertFalse(getAutofillAssistantSwitch(true));
-            PreferencesTest.clickPreference(autofillAssistantPrefs, onOffSwitch);
+            onOffSwitch.performClick();
             Assert.assertTrue(getAutofillAssistantSwitch(false));
 
             preferences.finish();
@@ -105,9 +105,9 @@
                         AutofillAssistantPreferences.class.getName());
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             AutofillAssistantPreferences autofillAssistantPrefs =
-                    (AutofillAssistantPreferences) preferences2.getMainFragment();
-            ChromeSwitchPreference onOffSwitch =
-                    (ChromeSwitchPreference) autofillAssistantPrefs.findPreference(
+                    (AutofillAssistantPreferences) preferences2.getMainFragmentCompat();
+            ChromeSwitchPreferenceCompat onOffSwitch =
+                    (ChromeSwitchPreferenceCompat) autofillAssistantPrefs.findPreference(
                             AutofillAssistantPreferences.PREF_AUTOFILL_ASSISTANT_SWITCH);
             Assert.assertFalse(onOffSwitch.isChecked());
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrDaydreamReadyModuleInstallTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrDaydreamReadyModuleInstallTest.java
index 0d6109b..175f5dd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrDaydreamReadyModuleInstallTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrDaydreamReadyModuleInstallTest.java
@@ -29,12 +29,17 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.components.module_installer.ModuleInstaller;
+import org.chromium.components.module_installer.ModuleInstallerRule;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.Callable;
 
 /**
  * End-to-end tests for installing the VR DFM on Daydream-ready phones on startup.
+ *
+ * TODO(agrieve): This test may be better as a robolectric test.
  */
 @RunWith(ParameterizedRunner.class)
 @UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
@@ -48,12 +53,24 @@
     @Rule
     public RuleChain mRuleChain;
 
+    private ModuleInstallerRule mModuleInstallerRule;
+
     private ChromeActivityTestRule mVrTestRule;
 
+    private final Set<String> mModulesRequestedDeferred = new HashSet<>();
+
     public VrDaydreamReadyModuleInstallTest(Callable<ChromeActivityTestRule> callable)
             throws Exception {
         mVrTestRule = callable.call();
-        mRuleChain = VrTestRuleUtils.wrapRuleInActivityRestrictionRule(mVrTestRule);
+        mModuleInstallerRule = new ModuleInstallerRule(new ModuleInstaller() {
+            @Override
+            public void installDeferred(String moduleName) {
+                mModulesRequestedDeferred.add(moduleName);
+            }
+        });
+        mRuleChain =
+                RuleChain.outerRule(mModuleInstallerRule)
+                        .around(VrTestRuleUtils.wrapRuleInActivityRestrictionRule(mVrTestRule));
     }
 
     /** Tests that the install is requested deferred. */
@@ -62,8 +79,8 @@
     @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
     @Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM})
     @VrModuleNotInstalled
-    public void testDeferredRequestOnStartup() throws InterruptedException {
+    public void testDeferredRequestOnStartup() {
         Assert.assertTrue("VR module should have been deferred installed at startup",
-                ModuleInstaller.didRequestDeferred("vr"));
+                mModulesRequestedDeferred.contains("vr"));
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/payments/PaymentManifestVerifierTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/payments/PaymentManifestVerifierTest.java
index 3ac117c..7d432e4 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/payments/PaymentManifestVerifierTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/payments/PaymentManifestVerifierTest.java
@@ -32,6 +32,8 @@
 @RunWith(RobolectricTestRunner.class)
 @Config(sdk = 21, manifest = Config.NONE)
 public class PaymentManifestVerifierTest {
+    private static final String ERROR_MESSAGE = "This is an error message.";
+
     private final URI mMethodName;
     private final ResolveInfo mAlicePay;
     private final ResolveInfo mBobPay;
@@ -139,7 +141,7 @@
                     @Override
                     public void downloadPaymentMethodManifest(
                             URI uri, ManifestDownloadCallback callback) {
-                        callback.onManifestDownloadFailure();
+                        callback.onManifestDownloadFailure(ERROR_MESSAGE);
                     }
 
                     @Override
@@ -167,7 +169,7 @@
 
                     @Override
                     public void downloadWebAppManifest(URI uri, ManifestDownloadCallback callback) {
-                        callback.onManifestDownloadFailure();
+                        callback.onManifestDownloadFailure(ERROR_MESSAGE);
                     }
 
                     @Override
@@ -288,7 +290,7 @@
             @Override
             public void downloadWebAppManifest(URI uri, ManifestDownloadCallback callback) {
                 if (mDownloadWebAppManifestCounter++ == 0) {
-                    callback.onManifestDownloadFailure();
+                    callback.onManifestDownloadFailure(ERROR_MESSAGE);
                 } else {
                     callback.onWebAppManifestDownloadSuccess("some content");
                 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
index 7bd08e10..942de06 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
@@ -1094,4 +1094,68 @@
         onGotManifestData(updateManager, defaultManifestData());
         assertTrue(updateManager.updateRequested());
     }
+
+    /**
+     * Tests that a forced update is requested and performed immediately if there is a material
+     * change to the manifest.
+     */
+    @Test
+    public void testForcedUpdateSuccess() throws Exception {
+        WebappDataStorage storage = getStorage(WEBAPK_PACKAGE_NAME);
+        storage.setShouldForceUpdate(true);
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(storage);
+        updateIfNeeded(updateManager);
+        assertTrue(updateManager.updateCheckStarted());
+        onGotDifferentData(updateManager);
+        assertTrue(updateManager.updateRequested());
+        tryCompletingUpdate(updateManager, storage, WebApkInstallResult.SUCCESS);
+        assertEquals(false, storage.shouldForceUpdate());
+    }
+
+    /**
+     * Tests that a forced update is requested, but not performed if there is no material change to
+     * the manifest.
+     */
+    @Test
+    public void testForcedUpdateNotNeeded() throws Exception {
+        WebappDataStorage storage = getStorage(WEBAPK_PACKAGE_NAME);
+        storage.setShouldForceUpdate(true);
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(storage);
+        updateIfNeeded(updateManager);
+        assertTrue(updateManager.updateCheckStarted());
+        onGotManifestData(updateManager, defaultManifestData());
+        assertFalse(updateManager.updateRequested());
+        assertEquals(false, storage.shouldForceUpdate());
+    }
+
+    /**
+     * Tests that a forced update handles failure gracefully.
+     */
+    @Test
+    public void testForcedUpdateFailure() throws Exception {
+        WebappDataStorage storage = getStorage(WEBAPK_PACKAGE_NAME);
+        storage.setShouldForceUpdate(true);
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(storage);
+        updateIfNeeded(updateManager);
+        assertTrue(updateManager.updateCheckStarted());
+        onGotDifferentData(updateManager);
+        assertTrue(updateManager.updateRequested());
+        tryCompletingUpdate(updateManager, storage, WebApkInstallResult.FAILURE);
+        assertEquals(false, storage.shouldForceUpdate());
+    }
+
+    /**
+     * Tests that a forced update handles failing to retrieve the manifest.
+     */
+    @Test
+    public void testForcedUpdateManifestNotRetrieved() throws Exception {
+        WebappDataStorage storage = getStorage(WEBAPK_PACKAGE_NAME);
+        storage.setShouldForceUpdate(true);
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(storage);
+        updateIfNeeded(updateManager);
+        assertTrue(updateManager.updateCheckStarted());
+        onGotManifestData(updateManager, null);
+        assertFalse(updateManager.updateRequested());
+        assertEquals(false, storage.shouldForceUpdate());
+    }
 }
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index a295d15..6a82b52 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-77.0.3849.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-77.0.3850.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
index ef04459..d3cd6e7 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
@@ -241,8 +241,9 @@
         Tab tab = getActivityTab();
         if (tab == null || tab.getUrl() == null || tab.getUrl().isEmpty()) return;
         long time = SystemClock.elapsedRealtime();
-        outState.putInt(BUNDLE_TAB_ID, tab.getId());
-        TabState.saveState(outState, TabState.from(tab));
+        if (TabState.saveState(outState, TabState.from(tab))) {
+            outState.putInt(BUNDLE_TAB_ID, tab.getId());
+        }
         RecordHistogram.recordTimesHistogram("Android.StrictMode.NoTouchActivitySaveState",
                 SystemClock.elapsedRealtime() - time);
     }
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java
index 1afba35..ceaf221 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java
@@ -13,7 +13,6 @@
 import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
-import org.chromium.chrome.browser.native_page.ContextMenuManager.ContextMenuItemId;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
 import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties;
 import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.ActionNames;
@@ -63,6 +62,8 @@
 
     private final ChromeActivity mActivity;
     private final ModalDialogManager mDialogManager;
+    // This field should be set when showing a dialog, and nulled out when the dialog is closed. We
+    // can check if it is null to determine whether we're currently showing our dialog.
     private PropertyModel mTouchlessMenuModel;
     private ModalDialogManager mModalDialogManager;
 
@@ -84,6 +85,11 @@
      */
     public void showTouchlessContextMenu(
             ModalDialogManager modalDialogManager, ContextMenuManager.Delegate delegate) {
+        // Don't create a new dialog if we're already showing one.
+        if (mTouchlessMenuModel != null) {
+            return;
+        }
+
         ArrayList<PropertyModel> menuItems = new ArrayList<>();
         for (@ContextMenuItemId int itemId = 0; itemId < ContextMenuItemId.NUM_ENTRIES; itemId++) {
             if (!shouldShowItem(itemId, delegate)) continue;
@@ -195,7 +201,9 @@
                             public void onClick(PropertyModel model, int buttonType) {}
 
                             @Override
-                            public void onDismiss(PropertyModel model, int dismissalCause) {}
+                            public void onDismiss(PropertyModel model, int dismissalCause) {
+                                mTouchlessMenuModel = null;
+                            }
                         })
                 .with(TouchlessDialogProperties.ACTION_NAMES, names)
                 .with(TouchlessDialogProperties.CANCEL_ACTION, (v) -> closeTouchlessContextMenu())
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/ui/progressbar/ProgressBarMediator.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/ui/progressbar/ProgressBarMediator.java
index 439993b..5c497ff 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/ui/progressbar/ProgressBarMediator.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/ui/progressbar/ProgressBarMediator.java
@@ -28,6 +28,7 @@
     private boolean mSkipShowingOnNextActivityStart;
 
     private static final int MINIMUM_DISPLAY_DURATION_MS = 3 * 1000;
+    private static final int MAXIMUM_PROGRESS = 100;
 
     ProgressBarMediator(PropertyModel model, ActivityTabProvider activityTabProvider) {
         mProgressBarTabObserver = new ProgressBarTabObserver(activityTabProvider);
@@ -73,11 +74,16 @@
         show();
     }
 
-    private void stopLoadProgress() {
+    private void finishLoadProgress() {
         mCanHideProgressBar = true;
         hide();
     }
 
+    private void updateLoadProgress(int progress) {
+        mModel.set(ProgressBarProperties.PROGRESS_FRACTION, progress / ((float) MAXIMUM_PROGRESS));
+        if (progress == MAXIMUM_PROGRESS) finishLoadProgress();
+    }
+
     void destroy() {
         if (mHideTask != null) mHideTask.cancel(false);
         mProgressBarTabObserver.destroy();
@@ -94,15 +100,23 @@
         public void onDidStartNavigation(Tab tab, NavigationHandle navigation) {
             if (!navigation.isInMainFrame()) return;
 
+            if (tab.getWebContents() != null
+                    && tab.getWebContents().getNavigationController() != null
+                    && tab.getWebContents().getNavigationController().isInitialNavigation()) {
+                updateUrl(tab);
+            }
+
+            if (navigation.isSameDocument()) return;
+
             if (NativePageFactory.isNativePageUrl(navigation.getUrl(), tab.isIncognito())) {
                 mModel.set(ProgressBarProperties.IS_ENABLED, false);
-                stopLoadProgress();
+                finishLoadProgress();
                 return;
             }
 
             mModel.set(ProgressBarProperties.IS_ENABLED, true);
-            updateUrl(tab);
             startLoadProgress();
+            updateLoadProgress(tab.getProgress());
         }
 
         @Override
@@ -117,15 +131,21 @@
 
         @Override
         public void onLoadStopped(Tab tab, boolean toDifferentDocument) {
-            updateUrl(tab);
-            stopLoadProgress();
+            if (!toDifferentDocument) return;
+
+            // If we made some progress, fast-forward to complete.
+            if (tab.getProgress() < MAXIMUM_PROGRESS) {
+                updateLoadProgress(MAXIMUM_PROGRESS);
+            } else {
+                finishLoadProgress();
+            }
         }
 
         @Override
         public void onLoadProgressChanged(Tab tab, int progress) {
             if (NativePageFactory.isNativePageUrl(tab.getUrl(), tab.isIncognito())) return;
 
-            mModel.set(ProgressBarProperties.PROGRESS_FRACTION, progress / 100f);
+            updateLoadProgress(progress);
         }
 
         @Override
@@ -133,12 +153,12 @@
             if (!didStartLoad) return;
 
             updateUrl(tab);
-            if (didFinishLoad) stopLoadProgress();
+            if (didFinishLoad) finishLoadProgress();
         }
 
         @Override
         public void onCrash(Tab tab) {
-            stopLoadProgress();
+            finishLoadProgress();
         }
 
         private void updateUrl(Tab tab) {
diff --git a/chrome/android/trichrome.gni b/chrome/android/trichrome.gni
index 758f1ca..abefaaf 100644
--- a/chrome/android/trichrome.gni
+++ b/chrome/android/trichrome.gni
@@ -49,6 +49,8 @@
                              "apk_name",
                              "min_sdk_version",
                              "proguard_jar_path",
+                             "shared_resources_whitelist_target",
+                             "shared_resources_whitelist_locales",
                              "static_library_dependent_targets",
                              "target_sdk_version",
                            ])
@@ -59,8 +61,12 @@
     no_build_hooks = true
 
     alternative_android_sdk_dep = webview_framework_dep
-    app_as_shared_lib = true
-    r_java_root_package_name = "trichrome_lib"
+    if (!trichrome_synchronized_proguard) {
+      # TODO(crbug.com/901465): Remove r_java_root_package_name once shared
+      # Java code is moved to the shared library even in debug.
+      r_java_root_package_name = "trichrome_lib"
+      app_as_shared_lib = true
+    }
     use_chromium_linker = false
     uncompress_shared_libraries = true
     uncompress_dex = use_uncompressed_dex
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index aeb27b76..2f1700b 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -9203,7 +9203,7 @@
       No nearby devices found.
     </message>
     <message name="IDS_BLUETOOTH_SCANNING_PROMPT_ORIGIN" desc="The label that is used to introduce Bluetooth scanning prompt details to the user when it is from a website.">
-      <ph name="Origin">$1<ex>www.google.com</ex></ph> wants to scan for nearby Bluetooth devices, the following devices have been found:
+      <ph name="Origin">$1<ex>www.google.com</ex></ph> wants to scan for nearby Bluetooth devices. The following devices have been found:
     </message>
     <message name="IDS_BLUETOOTH_SCANNING_PROMPT_ALLOW_BUTTON_TEXT" desc="Label on the button that allows Bluetooth scanning." formatter_data="android_java">
       Allow
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index a69266f..e404aef1 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -15,7 +15,7 @@
     Languages and input
   </message>
 
-  <!-- Personalizaton Page (OS settings) -->
+  <!-- Personalization Page (OS settings) -->
   <message name="IDS_OS_SETTINGS_PERSONALIZATION" desc="Name of the OS settings page which displays personalization preferences.">
     Personalization
   </message>
@@ -32,5 +32,10 @@
   <message name="IDS_OS_SETTINGS_ASSISTANT" desc="Name of the settings section for the Google Assistant.">
     Assistant
   </message>
+
+  <!-- Files Page (OS settings) -->
+  <message name="IDS_OS_SETTINGS_FILES" desc="Name of the settings page which displays file preferences.">
+    Files
+  </message>
 </if>
 </grit-part>
diff --git a/chrome/app/theme/theme_resources.grd b/chrome/app/theme/theme_resources.grd
index 1a4b27e..64705d8 100644
--- a/chrome/app/theme/theme_resources.grd
+++ b/chrome/app/theme/theme_resources.grd
@@ -262,7 +262,7 @@
         <structure type="chrome_scaled_image" name="IDR_LOGO_AVATAR_CIRCLE_BLUE_COLOR" file="cros/logo_avatar_circle_blue_color.png" />
         <structure type="chrome_scaled_image" name="IDR_LOGO_GOOGLE_COLOR_90" file="cros/logo_google_color_90.png" />
       </if>
-      <if expr="not_is_android">
+      <if expr="not is_android">
         <structure type="chrome_scaled_image" name="IDR_SUPERVISED_USER_THEME_FRAME" file="common/supervised_user_theme/theme_frame_supervised.png" />
         <structure type="chrome_scaled_image" name="IDR_SUPERVISED_USER_THEME_FRAME_INACTIVE" file="common/supervised_user_theme/theme_frame_supervised_inactive.png" />
         <structure type="chrome_scaled_image" name="IDR_SUPERVISED_USER_THEME_TAB_BACKGROUND" file="common/supervised_user_theme/theme_tab_background_supervised.png" />
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 3a7ca7f..3d7c37f 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -814,6 +814,8 @@
     "native_file_system/chrome_native_file_system_permission_context.h",
     "native_file_system/native_file_system_permission_context_factory.cc",
     "native_file_system/native_file_system_permission_context_factory.h",
+    "native_file_system/native_file_system_permission_request_manager.cc",
+    "native_file_system/native_file_system_permission_request_manager.h",
     "native_window_notification_source.h",
     "navigation_predictor/navigation_predictor.cc",
     "navigation_predictor/navigation_predictor.h",
@@ -1078,6 +1080,8 @@
     "performance_manager/persistence/site_data/feature_usage.h",
     "performance_manager/persistence/site_data/leveldb_site_data_store.cc",
     "performance_manager/persistence/site_data/leveldb_site_data_store.h",
+    "performance_manager/persistence/site_data/non_recording_site_data_cache.cc",
+    "performance_manager/persistence/site_data/non_recording_site_data_cache.h",
     "performance_manager/persistence/site_data/noop_site_data_writer.cc",
     "performance_manager/persistence/site_data/noop_site_data_writer.h",
     "performance_manager/persistence/site_data/site_data_cache.h",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 3c73a70..aab04ed 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2333,6 +2333,12 @@
      flag_descriptions::kArcVpnDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(arc::kVpnFeature)},
 #endif  // OS_CHROMEOS
+#if defined(OS_WIN)
+    {"enable-winrt-sensor-implementation",
+     flag_descriptions::kWinrtSensorsImplementationName,
+     flag_descriptions::kWinrtSensorsImplementationDescription, kOsWin,
+     FEATURE_VALUE_TYPE(features::kWinrtSensorsImplementation)},
+#endif
     {"enable-generic-sensor", flag_descriptions::kEnableGenericSensorName,
      flag_descriptions::kEnableGenericSensorDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kGenericSensor)},
@@ -2503,6 +2509,11 @@
      flag_descriptions::kOmniboxOnDeviceHeadSuggestionsName,
      flag_descriptions::kOmniboxOnDeviceHeadSuggestionsDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(omnibox::kOnDeviceHeadProvider)},
+
+    {"omnibox-search-engine-logo",
+     flag_descriptions::kOmniboxSearchEngineLogoName,
+     flag_descriptions::kOmniboxSearchEngineLogoDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(omnibox::kOmniboxSearchEngineLogo)},
 #endif  // defined(OS_ANDROID)
 
     {"omnibox-rich-entity-suggestions",
@@ -4093,6 +4104,10 @@
      flag_descriptions::kEnableAutofillSaveCardShowNoThanksDescription, kOsAll,
      FEATURE_VALUE_TYPE(autofill::features::kAutofillSaveCardShowNoThanks)},
 
+    {"enable-defer-all-script", flag_descriptions::kEnableDeferAllScriptName,
+     flag_descriptions::kEnableDeferAllScriptDescription, kOsAll,
+     FEATURE_VALUE_TYPE(previews::features::kDeferAllScriptPreviews)},
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 993cb50..1c52b61 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -208,6 +208,7 @@
     &omnibox::kOmniboxRichEntitySuggestions,
     &omnibox::kQueryInOmnibox,
     &omnibox::kUIExperimentShowSuggestionFavicons,
+    &omnibox::kOmniboxSearchEngineLogo,
     &password_manager::features::kGooglePasswordManager,
     &password_manager::features::kPasswordEditingAndroid,
     &password_manager::features::kTouchToFillAndroid,
diff --git a/chrome/browser/android/find_in_page/find_in_page_bridge.cc b/chrome/browser/android/find_in_page/find_in_page_bridge.cc
index 84b237d..6bcdff4 100644
--- a/chrome/browser/android/find_in_page/find_in_page_bridge.cc
+++ b/chrome/browser/android/find_in_page/find_in_page_bridge.cc
@@ -7,6 +7,7 @@
 #include "base/android/jni_string.h"
 #include "chrome/android/chrome_jni_headers/FindInPageBridge_jni.h"
 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
+#include "chrome/browser/ui/find_bar/find_types.h"
 #include "content/public/browser/web_contents.h"
 
 using base::android::ConvertUTF16ToJavaString;
@@ -40,9 +41,9 @@
 void FindInPageBridge::StopFinding(JNIEnv* env,
                                    const JavaParamRef<jobject>& obj,
                                    jboolean clearSelection) {
-  FindTabHelper::FromWebContents(web_contents_)->
-      StopFinding(clearSelection ? FindBarController::kClearSelectionOnPage
-          : FindBarController::kKeepSelectionOnPage);
+  FindTabHelper::FromWebContents(web_contents_)
+      ->StopFinding(clearSelection ? FindOnPageSelectionAction::kClear
+                                   : FindOnPageSelectionAction::kKeep);
 }
 
 ScopedJavaLocalRef<jstring> FindInPageBridge::GetPreviousFindText(
diff --git a/chrome/browser/android/payments/service_worker_payment_app_bridge.cc b/chrome/browser/android/payments/service_worker_payment_app_bridge.cc
index 5e44a549..656b87f 100644
--- a/chrome/browser/android/payments/service_worker_payment_app_bridge.cc
+++ b/chrome/browser/android/payments/service_worker_payment_app_bridge.cc
@@ -59,9 +59,15 @@
     const JavaRef<jobject>& jcallback,
     content::PaymentAppProvider::PaymentApps apps,
     payments::ServiceWorkerPaymentAppFactory::InstallablePaymentApps
-        installable_apps) {
+        installable_apps,
+    const std::string& error_message) {
   JNIEnv* env = AttachCurrentThread();
 
+  if (!error_message.empty()) {
+    Java_ServiceWorkerPaymentAppBridge_onGetPaymentAppsError(
+        env, jcallback, ConvertUTF8ToJavaString(env, error_message));
+  }
+
   for (const auto& app_info : apps) {
     // Sends related application Ids to java side if the app prefers related
     // applications.
diff --git a/chrome/browser/android/shortcut_helper.cc b/chrome/browser/android/shortcut_helper.cc
index 10704ea..4da3d1e 100644
--- a/chrome/browser/android/shortcut_helper.cc
+++ b/chrome/browser/android/shortcut_helper.cc
@@ -358,6 +358,7 @@
     const JavaParamRef<jobjectArray>& jnames,
     const JavaParamRef<jobjectArray>& jshort_names,
     const JavaParamRef<jobjectArray>& jpackage_names,
+    const JavaParamRef<jobjectArray>& jids,
     const JavaParamRef<jintArray>& jshell_apk_versions,
     const JavaParamRef<jintArray>& jversion_codes,
     const JavaParamRef<jobjectArray>& juris,
@@ -369,7 +370,8 @@
     const JavaParamRef<jlongArray>& jtheme_colors,
     const JavaParamRef<jlongArray>& jbackground_colors,
     const JavaParamRef<jlongArray>& jlast_update_check_times_ms,
-    const JavaParamRef<jbooleanArray>& jrelax_updates) {
+    const JavaParamRef<jbooleanArray>& jrelax_updates,
+    const JavaParamRef<jobjectArray>& jupdateStatuses) {
   DCHECK(jcallback_pointer);
   std::vector<std::string> names;
   base::android::AppendJavaStringArrayToStringVector(env, jnames, &names);
@@ -379,6 +381,8 @@
   std::vector<std::string> package_names;
   base::android::AppendJavaStringArrayToStringVector(env, jpackage_names,
                                                      &package_names);
+  std::vector<std::string> ids;
+  base::android::AppendJavaStringArrayToStringVector(env, jids, &ids);
   std::vector<int> shell_apk_versions;
   base::android::JavaIntArrayToIntVector(env, jshell_apk_versions,
                                          &shell_apk_versions);
@@ -409,9 +413,13 @@
   std::vector<bool> relax_updates;
   base::android::JavaBooleanArrayToBoolVector(env, jrelax_updates,
                                               &relax_updates);
+  std::vector<std::string> update_statuses;
+  base::android::AppendJavaStringArrayToStringVector(env, jupdateStatuses,
+                                                     &update_statuses);
 
   DCHECK(short_names.size() == names.size());
   DCHECK(short_names.size() == package_names.size());
+  DCHECK(short_names.size() == ids.size());
   DCHECK(short_names.size() == shell_apk_versions.size());
   DCHECK(short_names.size() == version_codes.size());
   DCHECK(short_names.size() == uris.size());
@@ -424,21 +432,22 @@
   DCHECK(short_names.size() == background_colors.size());
   DCHECK(short_names.size() == last_update_check_times_ms.size());
   DCHECK(short_names.size() == relax_updates.size());
+  DCHECK(short_names.size() == update_statuses.size());
 
   std::vector<WebApkInfo> webapk_list;
   webapk_list.reserve(short_names.size());
   for (size_t i = 0; i < short_names.size(); ++i) {
     webapk_list.push_back(WebApkInfo(
         std::move(names[i]), std::move(short_names[i]),
-        std::move(package_names[i]), shell_apk_versions[i], version_codes[i],
-        std::move(uris[i]), std::move(scopes[i]), std::move(manifest_urls[i]),
-        std::move(manifest_start_urls[i]),
+        std::move(package_names[i]), std::move(ids[i]), shell_apk_versions[i],
+        version_codes[i], std::move(uris[i]), std::move(scopes[i]),
+        std::move(manifest_urls[i]), std::move(manifest_start_urls[i]),
         static_cast<blink::WebDisplayMode>(display_modes[i]),
         static_cast<blink::WebScreenOrientationLockType>(orientations[i]),
         JavaColorToOptionalSkColor(theme_colors[i]),
         JavaColorToOptionalSkColor(background_colors[i]),
         base::Time::FromJavaTime(last_update_check_times_ms[i]),
-        relax_updates[i]));
+        relax_updates[i], std::move(update_statuses[i])));
   }
 
   ShortcutHelper::WebApkInfoCallback* webapk_list_callback =
@@ -446,3 +455,9 @@
   webapk_list_callback->Run(webapk_list);
   delete webapk_list_callback;
 }
+
+void ShortcutHelper::SetForceWebApkUpdate(const std::string& id) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_ShortcutHelper_setForceWebApkUpdate(
+      env, base::android::ConvertUTF8ToJavaString(env, id));
+}
diff --git a/chrome/browser/android/shortcut_helper.h b/chrome/browser/android/shortcut_helper.h
index 3edc5160..126359a 100644
--- a/chrome/browser/android/shortcut_helper.h
+++ b/chrome/browser/android/shortcut_helper.h
@@ -104,6 +104,10 @@
   // the info to the |callback|.
   static void RetrieveWebApks(const WebApkInfoCallback& callback);
 
+  // Sets a flag to force an update for the WebAPK corresponding to |id| on next
+  // launch.
+  static void SetForceWebApkUpdate(const std::string& id);
+
  private:
   DISALLOW_IMPLICIT_CONSTRUCTORS(ShortcutHelper);
 };
diff --git a/chrome/browser/android/signin/signin_manager_android.cc b/chrome/browser/android/signin/signin_manager_android.cc
index 54a9427..9936197 100644
--- a/chrome/browser/android/signin/signin_manager_android.cc
+++ b/chrome/browser/android/signin/signin_manager_android.cc
@@ -11,12 +11,11 @@
 #include "base/bind.h"
 #include "base/feature_list.h"
 #include "chrome/android/chrome_jni_headers/SigninManager_jni.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/primary_account_manager.h"
 #include "components/signin/public/base/account_consistency_method.h"
+#include "components/signin/public/base/signin_client.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/identity_manager/primary_account_mutator.h"
 #include "google_apis/gaia/gaia_auth_util.h"
@@ -25,28 +24,34 @@
 
 namespace {
 // Clears the information about the last signed-in user from |profile|.
-void ClearLastSignedInUserForProfile(Profile* profile) {
-  profile->GetPrefs()->ClearPref(prefs::kGoogleServicesLastAccountId);
-  profile->GetPrefs()->ClearPref(prefs::kGoogleServicesLastUsername);
+void ClearLastSignedInUserForProfile(SigninClient* signin_client) {
+  signin_client->GetPrefs()->ClearPref(prefs::kGoogleServicesLastAccountId);
+  signin_client->GetPrefs()->ClearPref(prefs::kGoogleServicesLastUsername);
 }
 }  // namespace
 
 SigninManagerAndroid::SigninManagerAndroid(
-    Profile* profile,
+    SigninClient* signin_client,
+    PrefService* local_state_pref_service,
     identity::IdentityManager* identity_manager,
     std::unique_ptr<SigninManagerDelegate> signin_manager_delegate)
-    : profile_(profile),
+    : signin_client_(signin_client),
       identity_manager_(identity_manager),
       signin_manager_delegate_(std::move(signin_manager_delegate)) {
-  DCHECK(profile_);
+  DCHECK(signin_client_);
+  DCHECK(local_state_pref_service);
   DCHECK(identity_manager_);
   DCHECK(signin_manager_delegate_);
   identity_manager_->AddObserver(this);
-  pref_change_registrar_.Init(profile_->GetPrefs());
-  pref_change_registrar_.Add(
-      prefs::kSigninAllowed,
+
+  signin_allowed_.Init(
+      prefs::kSigninAllowed, signin_client_->GetPrefs(),
       base::Bind(&SigninManagerAndroid::OnSigninAllowedPrefChanged,
                  base::Unretained(this)));
+
+  force_browser_signin_.Init(prefs::kForceBrowserSignin,
+                             local_state_pref_service);
+
   java_signin_manager_ = Java_SigninManager_create(
       base::android::AttachCurrentThread(), reinterpret_cast<intptr_t>(this),
       signin_manager_delegate_->GetJavaObject(),
@@ -98,7 +103,7 @@
 void SigninManagerAndroid::ClearLastSignedInUser(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj) {
-  ClearLastSignedInUserForProfile(profile_);
+  ClearLastSignedInUserForProfile(signin_client_);
 }
 
 void SigninManagerAndroid::LogInSignedInUser(JNIEnv* env,
@@ -109,18 +114,20 @@
   identity_manager_->LegacyReloadAccountsFromSystem();
 }
 
+bool SigninManagerAndroid::IsSigninAllowed() const {
+  return signin_allowed_.GetValue();
+}
+
 jboolean SigninManagerAndroid::IsSigninAllowedByPolicy(
     JNIEnv* env,
-    const JavaParamRef<jobject>& obj) {
-  return profile_->GetPrefs()->GetBoolean(prefs::kSigninAllowed);
+    const JavaParamRef<jobject>& obj) const {
+  return IsSigninAllowed();
 }
 
 jboolean SigninManagerAndroid::IsForceSigninEnabled(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj) {
-  // prefs::kForceBrowserSignin is set in Local State, not in user prefs.
-  PrefService* prefs = g_browser_process->local_state();
-  return prefs->GetBoolean(prefs::kForceBrowserSignin);
+  return force_browser_signin_.GetValue();
 }
 
 jboolean SigninManagerAndroid::IsSignedInOnNative(
@@ -136,10 +143,10 @@
                                      java_signin_manager_);
 }
 
-void SigninManagerAndroid::OnSigninAllowedPrefChanged() {
+void SigninManagerAndroid::OnSigninAllowedPrefChanged() const {
   Java_SigninManager_onSigninAllowedByPolicyChanged(
       base::android::AttachCurrentThread(), java_signin_manager_,
-      profile_->GetPrefs()->GetBoolean(prefs::kSigninAllowed));
+      IsSigninAllowed());
 }
 
 base::android::ScopedJavaLocalRef<jstring> JNI_SigninManager_ExtractDomainName(
diff --git a/chrome/browser/android/signin/signin_manager_android.h b/chrome/browser/android/signin/signin_manager_android.h
index 47de7dc..a03d97e 100644
--- a/chrome/browser/android/signin/signin_manager_android.h
+++ b/chrome/browser/android/signin/signin_manager_android.h
@@ -13,9 +13,10 @@
 #include "base/threading/thread_checker.h"
 #include "chrome/browser/android/signin/signin_manager_delegate.h"
 #include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_member.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 
-class Profile;
+class SigninClient;
 
 // Android wrapper of Chrome's C++ identity management code which provides
 // access from the Java layer. Note that on Android, there's only a single
@@ -28,7 +29,8 @@
 class SigninManagerAndroid : public identity::IdentityManager::Observer {
  public:
   SigninManagerAndroid(
-      Profile* profile,
+      SigninClient* signin_client,
+      PrefService* local_state_prefs_service,
       identity::IdentityManager* identity_manager,
       std::unique_ptr<SigninManagerDelegate> signin_manager_delegate);
 
@@ -56,7 +58,7 @@
 
   jboolean IsSigninAllowedByPolicy(
       JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj);
+      const base::android::JavaParamRef<jobject>& obj) const;
 
   jboolean IsForceSigninEnabled(
       JNIEnv* env,
@@ -70,9 +72,17 @@
       const CoreAccountInfo& previous_primary_account_info) override;
 
  private:
-  void OnSigninAllowedPrefChanged();
+  void OnSigninAllowedPrefChanged() const;
+  bool IsSigninAllowed() const;
 
-  Profile* profile_;
+  SigninClient* signin_client_;
+
+  // Handler for prefs::kSigninAllowed set in user's profile.
+  BooleanPrefMember signin_allowed_;
+
+  // Handler for prefs::kForceBrowserSignin. This preference is set in Local
+  // State, not in user prefs.
+  BooleanPrefMember force_browser_signin_;
 
   identity::IdentityManager* identity_manager_;
 
@@ -81,8 +91,6 @@
   // Java-side SigninManager object.
   base::android::ScopedJavaGlobalRef<jobject> java_signin_manager_;
 
-  PrefChangeRegistrar pref_change_registrar_;
-
   base::ThreadChecker thread_checker_;
 
   DISALLOW_COPY_AND_ASSIGN(SigninManagerAndroid);
diff --git a/chrome/browser/android/tab_web_contents_delegate_android.cc b/chrome/browser/android/tab_web_contents_delegate_android.cc
index 2449a42..4dc70e81 100644
--- a/chrome/browser/android/tab_web_contents_delegate_android.cc
+++ b/chrome/browser/android/tab_web_contents_delegate_android.cc
@@ -20,7 +20,6 @@
 #include "chrome/browser/android/feature_utilities.h"
 #include "chrome/browser/android/hung_renderer_infobar_delegate.h"
 #include "chrome/browser/banners/app_banner_manager_android.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/content_settings/sound_content_setting_observer.h"
 #include "chrome/browser/file_select_helper.h"
 #include "chrome/browser/infobars/infobar_service.h"
@@ -32,8 +31,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/resource_coordinator/tab_load_tracker.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
-#include "chrome/browser/ui/android/bluetooth_chooser_android.h"
-#include "chrome/browser/ui/android/bluetooth_scanning_prompt_android.h"
+#include "chrome/browser/ui/android/device_dialog/bluetooth_chooser_android.h"
+#include "chrome/browser/ui/android/device_dialog/bluetooth_scanning_prompt_android.h"
 #include "chrome/browser/ui/android/infobars/framebust_block_infobar.h"
 #include "chrome/browser/ui/android/tab_model/tab_model_list.h"
 #include "chrome/browser/ui/blocked_content/popup_blocker.h"
@@ -53,9 +52,6 @@
 #include "components/security_state/content/content_utils.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/navigation_entry.h"
-#include "content/public/browser/notification_details.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_source.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
@@ -135,9 +131,7 @@
     : WebContentsDelegateAndroid(env, obj) {
 }
 
-TabWebContentsDelegateAndroid::~TabWebContentsDelegateAndroid() {
-  notification_registrar_.RemoveAll();
-}
+TabWebContentsDelegateAndroid::~TabWebContentsDelegateAndroid() = default;
 
 void TabWebContentsDelegateAndroid::RunFileChooser(
     content::RenderFrameHost* render_frame_host,
@@ -174,14 +168,9 @@
 
 void TabWebContentsDelegateAndroid::CloseContents(
     WebContents* web_contents) {
-  // Prevent dangling registrations assigned to closed web contents.
-  if (notification_registrar_.IsRegistered(this,
-      chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
-      content::Source<WebContents>(web_contents))) {
-    notification_registrar_.Remove(this,
-        chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
-        content::Source<WebContents>(web_contents));
-  }
+  FindTabHelper* find_tab_helper = FindTabHelper::FromWebContents(web_contents);
+  if (find_result_observer_.IsObserving(find_tab_helper))
+    find_result_observer_.Remove(find_tab_helper);
 
   WebContentsDelegateAndroid::CloseContents(web_contents);
 }
@@ -202,16 +191,6 @@
   return false;
 }
 
-void TabWebContentsDelegateAndroid::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  DCHECK_EQ(chrome::NOTIFICATION_FIND_RESULT_AVAILABLE, type);
-  OnFindResultAvailable(
-      content::Source<WebContents>(source).ptr(),
-      content::Details<FindNotificationDetails>(details).ptr());
-}
-
 blink::WebDisplayMode TabWebContentsDelegateAndroid::GetDisplayMode(
     const WebContents* web_contents) {
   JNIEnv* env = base::android::AttachCurrentThread();
@@ -231,15 +210,10 @@
     const gfx::Rect& selection_rect,
     int active_match_ordinal,
     bool final_update) {
-  if (!notification_registrar_.IsRegistered(this,
-      chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
-      content::Source<WebContents>(web_contents))) {
-    notification_registrar_.Add(this,
-        chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
-        content::Source<WebContents>(web_contents));
-  }
-
   FindTabHelper* find_tab_helper = FindTabHelper::FromWebContents(web_contents);
+  if (!find_result_observer_.IsObserving(find_tab_helper))
+    find_result_observer_.Add(find_tab_helper);
+
   find_tab_helper->HandleFindReply(request_id,
                                    number_of_matches,
                                    selection_rect,
@@ -247,28 +221,6 @@
                                    final_update);
 }
 
-void TabWebContentsDelegateAndroid::OnFindResultAvailable(
-    WebContents* web_contents,
-    const FindNotificationDetails* find_result) {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
-  if (obj.is_null())
-    return;
-
-  ScopedJavaLocalRef<jobject> selection_rect =
-      JNI_TabWebContentsDelegateAndroid_CreateJavaRect(
-          env, find_result->selection_rect());
-
-  // Create the details object.
-  ScopedJavaLocalRef<jobject> details_object =
-      Java_TabWebContentsDelegateAndroid_createFindNotificationDetails(
-          env, find_result->number_of_matches(), selection_rect,
-          find_result->active_match_ordinal(), find_result->final_update());
-
-  Java_TabWebContentsDelegateAndroid_onFindResultAvailable(env, obj,
-                                                           details_object);
-}
-
 void TabWebContentsDelegateAndroid::FindMatchRectsReply(
     WebContents* web_contents,
     int version,
@@ -508,6 +460,30 @@
 }
 #endif
 
+void TabWebContentsDelegateAndroid::OnFindResultAvailable(
+    WebContents* web_contents) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env);
+  if (obj.is_null())
+    return;
+
+  const FindNotificationDetails& find_result =
+      FindTabHelper::FromWebContents(web_contents)->find_result();
+
+  ScopedJavaLocalRef<jobject> selection_rect =
+      JNI_TabWebContentsDelegateAndroid_CreateJavaRect(
+          env, find_result.selection_rect());
+
+  // Create the details object.
+  ScopedJavaLocalRef<jobject> details_object =
+      Java_TabWebContentsDelegateAndroid_createFindNotificationDetails(
+          env, find_result.number_of_matches(), selection_rect,
+          find_result.active_match_ordinal(), find_result.final_update());
+
+  Java_TabWebContentsDelegateAndroid_onFindResultAvailable(env, obj,
+                                                           details_object);
+}
+
 bool TabWebContentsDelegateAndroid::ShouldEnableEmbeddedMediaExperience()
     const {
   JNIEnv* env = base::android::AttachCurrentThread();
diff --git a/chrome/browser/android/tab_web_contents_delegate_android.h b/chrome/browser/android/tab_web_contents_delegate_android.h
index bf26bb5..bd8141e3 100644
--- a/chrome/browser/android/tab_web_contents_delegate_android.h
+++ b/chrome/browser/android/tab_web_contents_delegate_android.h
@@ -6,15 +6,15 @@
 #define CHROME_BROWSER_ANDROID_TAB_WEB_CONTENTS_DELEGATE_ANDROID_H_
 
 #include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "chrome/browser/ui/find_bar/find_result_observer.h"
+#include "chrome/browser/ui/find_bar/find_tab_helper.h"
 #include "components/embedder_support/android/delegate/web_contents_delegate_android.h"
 #include "content/public/browser/bluetooth_chooser.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
 #include "printing/buildflags/buildflags.h"
 #include "third_party/blink/public/common/frame/blocked_navigation_types.h"
 
-class FindNotificationDetails;
-
 namespace content {
 struct FileChooserParams;
 class WebContents;
@@ -32,7 +32,7 @@
 // the Chromium Android port but not to be shared with WebView.
 class TabWebContentsDelegateAndroid
     : public web_contents_delegate_android::WebContentsDelegateAndroid,
-      public content::NotificationObserver {
+      public FindResultObserver {
  public:
   TabWebContentsDelegateAndroid(JNIEnv* env, jobject obj);
   ~TabWebContentsDelegateAndroid() override;
@@ -112,21 +112,18 @@
       content::RenderFrameHost* subframe_host) const override;
 #endif
 
+  // FindResultObserver:
+  void OnFindResultAvailable(content::WebContents* web_contents) override;
+
   bool ShouldEnableEmbeddedMediaExperience() const;
   bool IsPictureInPictureEnabled() const;
   bool IsNightModeEnabled() const;
   const GURL GetManifestScope() const;
 
  private:
-  // NotificationObserver implementation.
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
+  ScopedObserver<FindTabHelper, FindResultObserver> find_result_observer_{this};
 
-  void OnFindResultAvailable(content::WebContents* web_contents,
-                             const FindNotificationDetails* find_result);
-
-  content::NotificationRegistrar notification_registrar_;
+  DISALLOW_COPY_AND_ASSIGN(TabWebContentsDelegateAndroid);
 };
 
 }  // namespace android
diff --git a/chrome/browser/android/usb/web_usb_chooser_android.cc b/chrome/browser/android/usb/web_usb_chooser_android.cc
index d715f49..62f5c70 100644
--- a/chrome/browser/android/usb/web_usb_chooser_android.cc
+++ b/chrome/browser/android/usb/web_usb_chooser_android.cc
@@ -7,7 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "chrome/browser/ui/android/usb_chooser_dialog_android.h"
+#include "chrome/browser/ui/android/device_dialog/usb_chooser_dialog_android.h"
 #include "chrome/browser/usb/usb_chooser_controller.h"
 
 WebUsbChooserAndroid::WebUsbChooserAndroid(
diff --git a/chrome/browser/android/vr/BUILD.gn b/chrome/browser/android/vr/BUILD.gn
index e17977b..d6c5c83 100644
--- a/chrome/browser/android/vr/BUILD.gn
+++ b/chrome/browser/android/vr/BUILD.gn
@@ -83,9 +83,9 @@
       "arcore_device/arcore_gl_thread.h",
       "arcore_device/arcore_impl.cc",
       "arcore_device/arcore_impl.h",
-      "arcore_device/arcore_install_utils.h",
       "arcore_device/arcore_java_utils.cc",
       "arcore_device/arcore_java_utils.h",
+      "arcore_device/arcore_session_utils.h",
       "arcore_device/arcore_shim.cc",
       "arcore_device/arcore_shim.h",
       "arcore_device/type_converters.cc",
@@ -222,6 +222,7 @@
 if (enable_arcore) {
   generate_jni("ar_jni_headers") {
     sources = [
+      "//chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreInstallUtils.java",
       "//chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java",
     ]
   }
diff --git a/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.cc b/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.cc
index b1fe89a..5b368c01 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.cc
@@ -4,13 +4,17 @@
 
 #include "chrome/browser/android/vr/arcore_device/arcore_consent_prompt.h"
 
+#include <memory>
 #include <utility>
 
 #include "base/bind.h"
 #include "chrome/android/features/vr/jni_headers/ArConsentDialog_jni.h"
 #include "chrome/browser/android/tab_android.h"
+#include "chrome/browser/android/vr/ar_jni_headers/ArCoreInstallUtils_jni.h"
+#include "chrome/browser/android/vr/arcore_device/arcore_device_provider.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
+#include "device/vr/android/arcore/arcore_device_provider_factory.h"
 
 using base::android::AttachCurrentThread;
 using base::android::ScopedJavaLocalRef;
@@ -19,7 +23,23 @@
 
 namespace {
 
-ArcoreConsentPrompt* g_instance = nullptr;
+ArCoreConsentPrompt* g_instance = nullptr;
+
+class ArCoreDeviceProviderFactoryImpl
+    : public device::ArCoreDeviceProviderFactory {
+ public:
+  ArCoreDeviceProviderFactoryImpl() = default;
+  ~ArCoreDeviceProviderFactoryImpl() override = default;
+  std::unique_ptr<device::VRDeviceProvider> CreateDeviceProvider() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ArCoreDeviceProviderFactoryImpl);
+};
+
+std::unique_ptr<device::VRDeviceProvider>
+ArCoreDeviceProviderFactoryImpl::CreateDeviceProvider() {
+  return std::make_unique<device::ArCoreDeviceProvider>();
+}
 
 base::android::ScopedJavaLocalRef<jobject> GetTabFromRenderer(
     int render_process_id,
@@ -44,42 +64,173 @@
 
 }  // namespace
 
-ArcoreConsentPrompt::ArcoreConsentPrompt() = default;
+ArCoreConsentPrompt::ArCoreConsentPrompt() : weak_ptr_factory_(this) {}
 
-ArcoreConsentPrompt::~ArcoreConsentPrompt() = default;
+ArCoreConsentPrompt::~ArCoreConsentPrompt() = default;
 
-void ArcoreConsentPrompt::GetUserPermission(
+void ArCoreConsentPrompt::GetUserPermission(
     int render_process_id,
     int render_frame_id,
     base::OnceCallback<void(bool)> response_callback) {
   on_user_consent_callback_ = std::move(response_callback);
+  render_process_id_ = render_process_id;
+  render_frame_id_ = render_frame_id;
 
   JNIEnv* env = AttachCurrentThread();
-  ScopedJavaLocalRef<jobject> jdelegate = Java_ArConsentDialog_showDialog(
+  jdelegate_ = Java_ArConsentDialog_showDialog(
       env, reinterpret_cast<jlong>(this),
-      GetTabFromRenderer(render_process_id, render_frame_id));
-  if (jdelegate.is_null()) {
+      GetTabFromRenderer(render_process_id_, render_frame_id_));
+  if (jdelegate_.is_null()) {
     std::move(on_user_consent_callback_).Run(false);
   }
 }
 
-void ArcoreConsentPrompt::OnUserConsentResult(
+void ArCoreConsentPrompt::OnUserConsentResult(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& j_caller,
     jboolean is_granted) {
-  DCHECK(on_user_consent_callback_);
-  std::move(on_user_consent_callback_).Run(is_granted);
+  jdelegate_.Reset();
+
+  if (!on_user_consent_callback_)
+    return;
+
+  if (!is_granted) {
+    CallDeferredUserConsentCallback(false);
+    return;
+  }
+
+  RequestArModule();
 }
 
 // static
-void ArcoreConsentPrompt::ShowConsentPrompt(
+void ArCoreConsentPrompt::ShowConsentPrompt(
     int render_process_id,
     int render_frame_id,
     base::OnceCallback<void(bool)> response_callback) {
   if (!g_instance)
-    g_instance = new ArcoreConsentPrompt();
+    g_instance = new ArCoreConsentPrompt();
   g_instance->GetUserPermission(render_process_id, render_frame_id,
                                 std::move(response_callback));
 }
 
+bool ArCoreConsentPrompt::CanRequestInstallArModule() {
+  return Java_ArCoreInstallUtils_canRequestInstallArModule(
+      AttachCurrentThread(), java_install_utils_);
+}
+
+bool ArCoreConsentPrompt::ShouldRequestInstallArModule() {
+  return Java_ArCoreInstallUtils_shouldRequestInstallArModule(
+      AttachCurrentThread(), java_install_utils_);
+}
+
+void ArCoreConsentPrompt::RequestInstallArModule() {
+  Java_ArCoreInstallUtils_requestInstallArModule(
+      AttachCurrentThread(), java_install_utils_,
+      GetTabFromRenderer(render_process_id_, render_frame_id_));
+}
+
+bool ArCoreConsentPrompt::ShouldRequestInstallSupportedArCore() {
+  JNIEnv* env = AttachCurrentThread();
+  return Java_ArCoreInstallUtils_shouldRequestInstallSupportedArCore(
+      env, java_install_utils_);
+}
+
+void ArCoreConsentPrompt::RequestInstallSupportedArCore() {
+  DCHECK(ShouldRequestInstallSupportedArCore());
+
+  JNIEnv* env = AttachCurrentThread();
+  Java_ArCoreInstallUtils_requestInstallSupportedArCore(
+      env, java_install_utils_,
+      GetTabFromRenderer(render_process_id_, render_frame_id_));
+}
+
+void ArCoreConsentPrompt::OnRequestInstallArModuleResult(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj,
+    bool success) {
+  DVLOG(1) << __func__;
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  if (on_request_ar_module_result_callback_) {
+    std::move(on_request_ar_module_result_callback_).Run(success);
+  }
+}
+
+void ArCoreConsentPrompt::OnRequestInstallSupportedArCoreResult(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj,
+    bool success) {
+  DVLOG(1) << __func__;
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(on_request_arcore_install_or_update_result_callback_);
+
+  std::move(on_request_arcore_install_or_update_result_callback_).Run(success);
+}
+
+void ArCoreConsentPrompt::RequestArModule() {
+  DVLOG(1) << __func__;
+  if (ShouldRequestInstallArModule()) {
+    if (!CanRequestInstallArModule()) {
+      OnRequestArModuleResult(false);
+      return;
+    }
+
+    on_request_ar_module_result_callback_ = base::BindOnce(
+        &ArCoreConsentPrompt::OnRequestArModuleResult, GetWeakPtr());
+    RequestInstallArModule();
+    return;
+  }
+
+  OnRequestArModuleResult(true);
+}
+
+void ArCoreConsentPrompt::OnRequestArModuleResult(bool success) {
+  DVLOG(3) << __func__ << ": success=" << success;
+
+  if (!success) {
+    CallDeferredUserConsentCallback(false);
+    return;
+  }
+
+  RequestArCoreInstallOrUpdate();
+}
+
+void ArCoreConsentPrompt::RequestArCoreInstallOrUpdate() {
+  DVLOG(1) << __func__;
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(!on_request_arcore_install_or_update_result_callback_);
+
+  if (ShouldRequestInstallSupportedArCore()) {
+    // ARCore is not installed or requires an update. Store the callback to be
+    // processed later once installation/update is complete or got cancelled.
+    on_request_arcore_install_or_update_result_callback_ = base::BindOnce(
+        &ArCoreConsentPrompt::OnRequestArCoreInstallOrUpdateResult,
+        GetWeakPtr());
+
+    RequestInstallSupportedArCore();
+    return;
+  }
+
+  OnRequestArCoreInstallOrUpdateResult(true);
+}
+
+void ArCoreConsentPrompt::OnRequestArCoreInstallOrUpdateResult(bool success) {
+  DVLOG(1) << __func__;
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  CallDeferredUserConsentCallback(success);
+}
+
+void ArCoreConsentPrompt::CallDeferredUserConsentCallback(
+    bool is_permission_granted) {
+  if (on_user_consent_callback_)
+    std::move(on_user_consent_callback_).Run(is_permission_granted);
+}
+
+static void JNI_ArCoreInstallUtils_InstallArCoreDeviceProviderFactory(
+    JNIEnv* env) {
+  device::ArCoreDeviceProviderFactory::Install(
+      std::make_unique<ArCoreDeviceProviderFactoryImpl>());
+}
+
 }  // namespace vr
diff --git a/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.h b/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.h
index 257d5ab..3e24ec6 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_consent_prompt.h
@@ -7,21 +7,24 @@
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_weak_ref.h"
+#include "base/android/scoped_java_ref.h"
 #include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
 #include "chrome/browser/vr/service/arcore_consent_prompt_interface.h"
 #include "chrome/browser/vr/vr_export.h"
 
 namespace vr {
 
-class VR_EXPORT ArcoreConsentPrompt : public ArcoreConsentPromptInterface {
+class VR_EXPORT ArCoreConsentPrompt : public ArCoreConsentPromptInterface {
  public:
   void ShowConsentPrompt(
       int render_process_id,
       int render_frame_id,
       base::OnceCallback<void(bool)> response_callback) override;
 
-  ArcoreConsentPrompt();
-  ~ArcoreConsentPrompt();
+  ArCoreConsentPrompt();
+  ~ArCoreConsentPrompt();
 
   // device::VrDevicePermissionProvider:
   void GetUserPermission(int render_process_id,
@@ -32,8 +35,51 @@
                            const base::android::JavaParamRef<jobject>& j_caller,
                            jboolean is_granted);
 
+  // Returns true if AR module installation is supported, false otherwise.
+  virtual bool CanRequestInstallArModule();
+  // Returns true if AR module is not installed, false otherwise.
+  virtual bool ShouldRequestInstallArModule();
+  virtual void RequestInstallArModule();
+  virtual bool ShouldRequestInstallSupportedArCore();
+  virtual void RequestInstallSupportedArCore();
+
+  // Called from Java end.
+  void OnRequestInstallArModuleResult(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj,
+      bool success);
+  void OnRequestInstallSupportedArCoreResult(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj,
+      bool success);
+
  private:
+  void RequestArModule();
+  void OnRequestArModuleResult(bool success);
+  void RequestArCoreInstallOrUpdate();
+  void OnRequestArCoreInstallOrUpdateResult(bool success);
+
+  void CallDeferredUserConsentCallback(bool is_permission_granted);
+
+  base::WeakPtr<ArCoreConsentPrompt> GetWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
   base::OnceCallback<void(bool)> on_user_consent_callback_;
+
+  base::OnceCallback<void(bool)> on_request_ar_module_result_callback_;
+  base::OnceCallback<void(bool)>
+      on_request_arcore_install_or_update_result_callback_;
+
+  base::android::ScopedJavaLocalRef<jobject> jdelegate_;
+  int render_process_id_;
+  int render_frame_id_;
+
+  base::android::ScopedJavaGlobalRef<jobject> java_install_utils_;
+  THREAD_CHECKER(thread_checker_);
+
+  base::WeakPtrFactory<ArCoreConsentPrompt> weak_ptr_factory_;
+  DISALLOW_COPY_AND_ASSIGN(ArCoreConsentPrompt);
 };
 
 }  // namespace vr
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device.cc b/chrome/browser/android/vr/arcore_device/arcore_device.cc
index 535231c..205e130 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_device.cc
@@ -14,8 +14,8 @@
 #include "chrome/browser/android/vr/arcore_device/arcore_gl.h"
 #include "chrome/browser/android/vr/arcore_device/arcore_gl_thread.h"
 #include "chrome/browser/android/vr/arcore_device/arcore_impl.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_install_utils.h"
 #include "chrome/browser/android/vr/arcore_device/arcore_java_utils.h"
+#include "chrome/browser/android/vr/arcore_device/arcore_session_utils.h"
 #include "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
 #include "chrome/browser/permissions/permission_manager.h"
 #include "chrome/browser/permissions/permission_result.h"
@@ -74,13 +74,13 @@
     std::unique_ptr<ArCoreFactory> arcore_factory,
     std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory,
     std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_to_surface_bridge,
-    std::unique_ptr<vr::ArCoreInstallUtils> arcore_install_utils)
+    std::unique_ptr<vr::ArCoreSessionUtils> arcore_session_utils)
     : VRDeviceBase(mojom::XRDeviceId::ARCORE_DEVICE_ID),
       main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       arcore_factory_(std::move(arcore_factory)),
       ar_image_transport_factory_(std::move(ar_image_transport_factory)),
       mailbox_bridge_(std::move(mailbox_to_surface_bridge)),
-      arcore_install_utils_(std::move(arcore_install_utils)),
+      arcore_session_utils_(std::move(arcore_session_utils)),
       session_state_(std::make_unique<ArCoreDevice::SessionState>()),
       weak_ptr_factory_(this) {
   // Ensure display_info_ is set to avoid crash in CallDeferredSessionCallback
@@ -97,25 +97,16 @@
 }
 
 ArCoreDevice::ArCoreDevice()
-    : ArCoreDevice(
-          std::make_unique<ArCoreImplFactory>(),
-          std::make_unique<ArImageTransportFactory>(),
-          std::make_unique<vr::MailboxToSurfaceBridge>(),
-          std::make_unique<vr::ArCoreJavaUtils>(
-              base::BindRepeating(
-                  &ArCoreDevice::OnRequestInstallArModuleResult,
-                  base::Unretained(this)),  // unretained is fine for callbacks
-                                            // since ArCoreDevice owns the
-                                            // ArCoreJavaUtils instance
-              base::BindRepeating(
-                  &ArCoreDevice::OnRequestInstallSupportedArCoreResult,
-                  base::Unretained(this)))) {}
+    : ArCoreDevice(std::make_unique<ArCoreImplFactory>(),
+                   std::make_unique<ArImageTransportFactory>(),
+                   std::make_unique<vr::MailboxToSurfaceBridge>(),
+                   std::make_unique<vr::ArCoreJavaUtils>()) {}
 
 ArCoreDevice::~ArCoreDevice() {
   CallDeferredRequestSessionCallback(/*success=*/false);
   // The GL thread must be terminated since it uses our members. For example,
   // there might still be a posted Initialize() call in flight that uses
-  // arcore_install_utils_ and arcore_factory_. Ensure that the thread is
+  // arcore_session_utils_ and arcore_factory_. Ensure that the thread is
   // stopped before other members get destructed. Don't call Stop() here,
   // destruction calls Stop() and doing so twice is illegal (null pointer
   // dereference).
@@ -190,15 +181,6 @@
 
 void ArCoreDevice::RequestSessionAfterInitialization(int render_process_id,
                                                      int render_frame_id) {
-  session_state_->start_immersive_activity_callback_ =
-      base::BindOnce(&ArCoreDevice::RequestArSessionConsent, GetWeakPtr(),
-                     render_process_id, render_frame_id);
-
-  RequestArModule(render_process_id, render_frame_id);
-}
-
-void ArCoreDevice::RequestArSessionConsent(int render_process_id,
-                                           int render_frame_id) {
   auto ready_callback =
       base::BindRepeating(&ArCoreDevice::OnDrawingSurfaceReady, GetWeakPtr());
   auto touch_callback =
@@ -206,7 +188,7 @@
   auto destroyed_callback =
       base::BindOnce(&ArCoreDevice::OnDrawingSurfaceDestroyed, GetWeakPtr());
 
-  arcore_install_utils_->RequestArSession(
+  arcore_session_utils_->RequestArSession(
       render_process_id, render_frame_id, std::move(ready_callback),
       std::move(touch_callback), std::move(destroyed_callback));
 }
@@ -250,7 +232,7 @@
   DVLOG(1) << __func__;
 
   // This may be a no-op in case it's destroyed already.
-  arcore_install_utils_->DestroyDrawingSurface();
+  arcore_session_utils_->DestroyDrawingSurface();
 
   // The GL thread had initialized its context with a drawing_widget based on
   // the ArImmersiveOverlay's Surface, and the one it has is no longer valid.
@@ -276,88 +258,6 @@
   mailbox_bridge_ = nullptr;
 }
 
-void ArCoreDevice::RequestArModule(int render_process_id, int render_frame_id) {
-  DVLOG(1) << __func__;
-  if (arcore_install_utils_->ShouldRequestInstallArModule()) {
-    if (!arcore_install_utils_->CanRequestInstallArModule()) {
-      OnRequestArModuleResult(render_process_id, render_frame_id, false);
-      return;
-    }
-
-    on_request_ar_module_result_callback_ =
-        base::BindOnce(&ArCoreDevice::OnRequestArModuleResult, GetWeakPtr(),
-                       render_process_id, render_frame_id);
-    arcore_install_utils_->RequestInstallArModule(render_process_id,
-                                                  render_frame_id);
-    return;
-  }
-
-  OnRequestArModuleResult(render_process_id, render_frame_id, true);
-}
-
-void ArCoreDevice::OnRequestArModuleResult(int render_process_id,
-                                           int render_frame_id,
-                                           bool success) {
-  DVLOG(3) << __func__ << ": success=" << success;
-
-  if (!success) {
-    CallDeferredRequestSessionCallback(/*success=*/false);
-    return;
-  }
-
-  RequestArCoreInstallOrUpdate(render_process_id, render_frame_id);
-}
-
-void ArCoreDevice::RequestArCoreInstallOrUpdate(int render_process_id,
-                                                int render_frame_id) {
-  DVLOG(1) << __func__;
-  DCHECK(IsOnMainThread());
-  DCHECK(!on_request_arcore_install_or_update_result_callback_);
-
-  if (arcore_install_utils_->ShouldRequestInstallSupportedArCore()) {
-    // ARCore is not installed or requires an update. Store the callback to be
-    // processed later once installation/update is complete or got cancelled.
-    on_request_arcore_install_or_update_result_callback_ = base::BindOnce(
-        &ArCoreDevice::OnRequestArCoreInstallOrUpdateResult, GetWeakPtr());
-
-    arcore_install_utils_->RequestInstallSupportedArCore(render_process_id,
-                                                         render_frame_id);
-    return;
-  }
-
-  OnRequestArCoreInstallOrUpdateResult(true);
-}
-
-void ArCoreDevice::OnRequestArCoreInstallOrUpdateResult(bool success) {
-  DVLOG(1) << __func__;
-  DCHECK(IsOnMainThread());
-
-  if (!success) {
-    CallDeferredRequestSessionCallback(/*success=*/false);
-    return;
-  }
-
-  DCHECK(session_state_->start_immersive_activity_callback_);
-  std::move(session_state_->start_immersive_activity_callback_).Run();
-}
-
-void ArCoreDevice::OnRequestInstallArModuleResult(bool success) {
-  DVLOG(1) << __func__;
-  DCHECK(IsOnMainThread());
-
-  if (on_request_ar_module_result_callback_) {
-    std::move(on_request_ar_module_result_callback_).Run(success);
-  }
-}
-
-void ArCoreDevice::OnRequestInstallSupportedArCoreResult(bool success) {
-  DVLOG(1) << __func__;
-  DCHECK(IsOnMainThread());
-  DCHECK(on_request_arcore_install_or_update_result_callback_);
-
-  std::move(on_request_arcore_install_or_update_result_callback_).Run(success);
-}
-
 void ArCoreDevice::CallDeferredRequestSessionCallback(bool success) {
   DVLOG(1) << __func__ << " success=" << success;
   DCHECK(IsOnMainThread());
@@ -430,7 +330,7 @@
   DCHECK(IsOnMainThread());
   DCHECK(session_state_->is_arcore_gl_thread_initialized_);
 
-  if (!arcore_install_utils_->EnsureLoaded()) {
+  if (!arcore_session_utils_->EnsureLoaded()) {
     DLOG(ERROR) << "ARCore was not loaded properly.";
     OnArCoreGlInitializationComplete(false);
     return;
@@ -445,7 +345,7 @@
     PostTaskToGlThread(base::BindOnce(
         &ArCoreGl::Initialize,
         session_state_->arcore_gl_thread_->GetArCoreGl()->GetWeakPtr(),
-        arcore_install_utils_.get(), arcore_factory_.get(), drawing_widget,
+        arcore_session_utils_.get(), arcore_factory_.get(), drawing_widget,
         frame_size, rotation,
         CreateMainThreadCallback(base::BindOnce(
             &ArCoreDevice::OnArCoreGlInitializationComplete, GetWeakPtr()))));
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device.h b/chrome/browser/android/vr/arcore_device/arcore_device.h
index fc2560b..64a4e79 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_device.h
@@ -22,7 +22,7 @@
 
 namespace vr {
 class MailboxToSurfaceBridge;
-class ArCoreInstallUtils;
+class ArCoreSessionUtils;
 }  // namespace vr
 
 namespace device {
@@ -37,7 +37,7 @@
       std::unique_ptr<ArCoreFactory> arcore_factory,
       std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory,
       std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_to_surface_bridge,
-      std::unique_ptr<vr::ArCoreInstallUtils> arcore_install_utils);
+      std::unique_ptr<vr::ArCoreSessionUtils> arcore_session_utils);
   ArCoreDevice();
   ~ArCoreDevice() override;
 
@@ -51,9 +51,6 @@
   }
 
  private:
-  void OnRequestInstallArModuleResult(bool success);
-  void OnRequestInstallSupportedArCoreResult(bool success);
-
   // VRDeviceBase implementation
   void OnMailboxBridgeReady();
   void OnArCoreGlThreadInitialized();
@@ -102,7 +99,6 @@
                                      int rotation,
                                      const gfx::Size& size);
   void OnArCoreGlInitializationComplete(bool success);
-  void RequestArSessionConsent(int render_process_id, int render_frame_id);
 
   void OnCreateSessionCallback(
       mojom::XRRuntime::RequestSessionCallback deferred_callback,
@@ -115,7 +111,7 @@
   std::unique_ptr<ArCoreFactory> arcore_factory_;
   std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory_;
   std::unique_ptr<vr::MailboxToSurfaceBridge> mailbox_bridge_;
-  std::unique_ptr<vr::ArCoreInstallUtils> arcore_install_utils_;
+  std::unique_ptr<vr::ArCoreSessionUtils> arcore_session_utils_;
 
   // Encapsulates data with session lifetime.
   struct SessionState {
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc b/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc
index cc514c7..72706d6 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc
@@ -11,9 +11,8 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "chrome/browser/android/vr/arcore_device/ar_image_transport.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_device.h"
 #include "chrome/browser/android/vr/arcore_device/arcore_gl.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_install_utils.h"
+#include "chrome/browser/android/vr/arcore_device/arcore_session_utils.h"
 #include "chrome/browser/android/vr/arcore_device/fake_arcore.h"
 #include "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
@@ -82,18 +81,10 @@
   base::OnceClosure callback_;
 };
 
-class StubArCoreInstallUtils : public vr::ArCoreInstallUtils {
+class StubArCoreSessionUtils : public vr::ArCoreSessionUtils {
  public:
-  StubArCoreInstallUtils() = default;
+  StubArCoreSessionUtils() = default;
 
-  bool CanRequestInstallArModule() override { return false; }
-  bool ShouldRequestInstallArModule() override { return false; }
-
-  void RequestInstallArModule(int render_process_id,
-                              int render_frame_id) override {}
-  bool ShouldRequestInstallSupportedArCore() override { return false; }
-  void RequestInstallSupportedArCore(int render_process_id,
-                                     int render_frame_id) override {}
   void RequestArSession(
       int render_process_id,
       int render_frame_id,
@@ -148,7 +139,7 @@
   }
 
   StubMailboxToSurfaceBridge* bridge;
-  StubArCoreInstallUtils* install_utils;
+  StubArCoreSessionUtils* session_utils;
   mojom::XRFrameDataProviderPtr frame_provider;
   mojom::XREnvironmentIntegrationProviderAssociatedPtr environment_provider;
   std::unique_ptr<base::RunLoop> run_loop;
@@ -159,13 +150,13 @@
     std::unique_ptr<StubMailboxToSurfaceBridge> bridge_ptr =
         std::make_unique<StubMailboxToSurfaceBridge>();
     bridge = bridge_ptr.get();
-    std::unique_ptr<StubArCoreInstallUtils> install_utils_ptr =
-        std::make_unique<StubArCoreInstallUtils>();
-    install_utils = install_utils_ptr.get();
+    std::unique_ptr<StubArCoreSessionUtils> session_utils_ptr =
+        std::make_unique<StubArCoreSessionUtils>();
+    session_utils = session_utils_ptr.get();
     device_ = std::make_unique<ArCoreDevice>(
         std::make_unique<FakeArCoreFactory>(),
         std::make_unique<StubArImageTransportFactory>(), std::move(bridge_ptr),
-        std::move(install_utils_ptr));
+        std::move(session_utils_ptr));
   }
 
   void CreateSession() {
@@ -245,7 +236,7 @@
                                        base::BindOnce(callback, &hit_results));
   // Have to get frame data to trigger the hit-test calculation.
   GetFrameData();
-  EXPECT_TRUE(hit_results.size() > 0);
+  EXPECT_FALSE(hit_results.empty());
 }
 
 }  // namespace device
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.cc b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
index 1bfb8e0..a466b51 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
@@ -20,7 +20,7 @@
 #include "base/trace_event/traced_value.h"
 #include "chrome/browser/android/vr/arcore_device/ar_image_transport.h"
 #include "chrome/browser/android/vr/arcore_device/arcore_impl.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_install_utils.h"
+#include "chrome/browser/android/vr/arcore_device/arcore_session_utils.h"
 #include "chrome/browser/android/vr/web_xr_presentation_state.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
 #include "gpu/ipc/common/gpu_memory_buffer_impl_android_hardware_buffer.h"
@@ -119,7 +119,7 @@
   CloseBindingsIfOpen();
 }
 
-void ArCoreGl::Initialize(vr::ArCoreInstallUtils* install_utils,
+void ArCoreGl::Initialize(vr::ArCoreSessionUtils* session_utils,
                           ArCoreFactory* arcore_factory,
                           gfx::AcceleratedWidget drawing_widget,
                           const gfx::Size& frame_size,
@@ -141,7 +141,7 @@
 
   // Get the activity context.
   base::android::ScopedJavaLocalRef<jobject> application_context =
-      install_utils->GetApplicationContext();
+      session_utils->GetApplicationContext();
   if (!application_context.obj()) {
     DLOG(ERROR) << "Unable to retrieve the Java context/activity!";
     std::move(callback).Run(false);
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.h b/chrome/browser/android/vr/arcore_device/arcore_gl.h
index 0e13a647..0121a4d8 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl.h
@@ -36,7 +36,7 @@
 }  // namespace gl
 
 namespace vr {
-class ArCoreInstallUtils;
+class ArCoreSessionUtils;
 class WebXrPresentationState;
 }  // namespace vr
 
@@ -63,7 +63,7 @@
   explicit ArCoreGl(std::unique_ptr<ArImageTransport> ar_image_transport);
   ~ArCoreGl() override;
 
-  void Initialize(vr::ArCoreInstallUtils* install_utils,
+  void Initialize(vr::ArCoreSessionUtils* session_utils,
                   ArCoreFactory* arcore_factory,
                   gfx::AcceleratedWidget drawing_widget,
                   const gfx::Size& frame_size,
diff --git a/chrome/browser/android/vr/arcore_device/arcore_java_utils.cc b/chrome/browser/android/vr/arcore_device/arcore_java_utils.cc
index 111e9a3..54392cd 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_java_utils.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_java_utils.cc
@@ -10,11 +10,9 @@
 #include "base/android/jni_string.h"
 #include "chrome/browser/android/tab_android.h"
 #include "chrome/browser/android/vr/ar_jni_headers/ArCoreJavaUtils_jni.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_device_provider.h"
 #include "chrome/browser/android/vr/arcore_device/arcore_shim.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
-#include "device/vr/android/arcore/arcore_device_provider_factory.h"
 
 using base::android::AttachCurrentThread;
 using base::android::ScopedJavaLocalRef;
@@ -23,29 +21,9 @@
 
 namespace {
 
-class ArCoreDeviceProviderFactoryImpl
-    : public device::ArCoreDeviceProviderFactory {
- public:
-  ArCoreDeviceProviderFactoryImpl() = default;
-  ~ArCoreDeviceProviderFactoryImpl() override = default;
-  std::unique_ptr<device::VRDeviceProvider> CreateDeviceProvider() override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ArCoreDeviceProviderFactoryImpl);
-};
-
-std::unique_ptr<device::VRDeviceProvider>
-ArCoreDeviceProviderFactoryImpl::CreateDeviceProvider() {
-  return std::make_unique<device::ArCoreDeviceProvider>();
-}
-
 }  // namespace
 
-ArCoreJavaUtils::ArCoreJavaUtils(
-    base::RepeatingCallback<void(bool)> ar_module_installation_callback,
-    base::RepeatingCallback<void(bool)> ar_core_installation_callback)
-    : ar_module_installation_callback_(ar_module_installation_callback),
-      ar_core_installation_callback_(ar_core_installation_callback) {
+ArCoreJavaUtils::ArCoreJavaUtils() {
   JNIEnv* env = AttachCurrentThread();
   if (!env)
     return;
@@ -61,46 +39,6 @@
   Java_ArCoreJavaUtils_onNativeDestroy(env, j_arcore_java_utils_);
 }
 
-void ArCoreJavaUtils::OnRequestInstallSupportedArCoreResult(
-    JNIEnv* env,
-    const base::android::JavaParamRef<jobject>& obj,
-    bool success) {
-  ar_core_installation_callback_.Run(success);
-}
-
-bool ArCoreJavaUtils::CanRequestInstallArModule() {
-  return Java_ArCoreJavaUtils_canRequestInstallArModule(AttachCurrentThread(),
-                                                        j_arcore_java_utils_);
-}
-
-bool ArCoreJavaUtils::ShouldRequestInstallArModule() {
-  return Java_ArCoreJavaUtils_shouldRequestInstallArModule(
-      AttachCurrentThread(), j_arcore_java_utils_);
-}
-
-void ArCoreJavaUtils::RequestInstallArModule(int render_process_id,
-                                             int render_frame_id) {
-  Java_ArCoreJavaUtils_requestInstallArModule(
-      AttachCurrentThread(), j_arcore_java_utils_,
-      getTabFromRenderer(render_process_id, render_frame_id));
-}
-
-bool ArCoreJavaUtils::ShouldRequestInstallSupportedArCore() {
-  JNIEnv* env = AttachCurrentThread();
-  return Java_ArCoreJavaUtils_shouldRequestInstallSupportedArCore(
-      env, j_arcore_java_utils_);
-}
-
-void ArCoreJavaUtils::RequestInstallSupportedArCore(int render_process_id,
-                                                    int render_frame_id) {
-  DCHECK(ShouldRequestInstallSupportedArCore());
-
-  JNIEnv* env = AttachCurrentThread();
-  Java_ArCoreJavaUtils_requestInstallSupportedArCore(
-      env, j_arcore_java_utils_,
-      getTabFromRenderer(render_process_id, render_frame_id));
-}
-
 void ArCoreJavaUtils::RequestArSession(
     int render_process_id,
     int render_frame_id,
@@ -161,13 +99,6 @@
   }
 }
 
-void ArCoreJavaUtils::OnRequestInstallArModuleResult(
-    JNIEnv* env,
-    const base::android::JavaParamRef<jobject>& obj,
-    bool success) {
-  ar_module_installation_callback_.Run(success);
-}
-
 bool ArCoreJavaUtils::EnsureLoaded() {
   DCHECK(vr::IsArCoreSupported());
 
@@ -206,10 +137,4 @@
   return j_tab_android;
 }
 
-static void JNI_ArCoreJavaUtils_InstallArCoreDeviceProviderFactory(
-    JNIEnv* env) {
-  device::ArCoreDeviceProviderFactory::Install(
-      std::make_unique<ArCoreDeviceProviderFactoryImpl>());
-}
-
 }  // namespace vr
diff --git a/chrome/browser/android/vr/arcore_device/arcore_java_utils.h b/chrome/browser/android/vr/arcore_device/arcore_java_utils.h
index 5a5afe5..9e9ac15 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_java_utils.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_java_utils.h
@@ -11,39 +11,26 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/android/vr/arcore_device/arcore_install_utils.h"
+#include "chrome/browser/android/vr/arcore_device/arcore_session_utils.h"
 
 namespace vr {
 
-class ArCoreJavaUtils : public ArCoreInstallUtils {
+class ArCoreJavaUtils : public ArCoreSessionUtils {
  public:
-  explicit ArCoreJavaUtils(
-      base::RepeatingCallback<void(bool)> ar_module_installation_callback,
-      base::RepeatingCallback<void(bool)> ar_core_installation_callback);
+  ArCoreJavaUtils();
   ~ArCoreJavaUtils() override;
-  bool ShouldRequestInstallArModule() override;
-  bool CanRequestInstallArModule() override;
-  void RequestInstallArModule(int render_process_id,
-                              int render_frame_id) override;
-  bool ShouldRequestInstallSupportedArCore() override;
-  void RequestInstallSupportedArCore(int render_process_id,
-                                     int render_frame_id) override;
+
+  // ArCoreSessionUtils:
   void RequestArSession(int render_process_id,
                         int render_frame_id,
                         SurfaceReadyCallback ready_callback,
                         SurfaceTouchCallback touch_callback,
                         SurfaceDestroyedCallback destroyed_callback) override;
   void DestroyDrawingSurface() override;
+  bool EnsureLoaded() override;
+  base::android::ScopedJavaLocalRef<jobject> GetApplicationContext() override;
 
   // Methods called from the Java side.
-  void OnRequestInstallArModuleResult(
-      JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj,
-      bool success);
-  void OnRequestInstallSupportedArCoreResult(
-      JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj,
-      bool success);
   void OnDrawingSurfaceReady(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj,
@@ -60,17 +47,11 @@
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj);
 
-  bool EnsureLoaded() override;
-  base::android::ScopedJavaLocalRef<jobject> GetApplicationContext() override;
-
  private:
   base::android::ScopedJavaLocalRef<jobject> getTabFromRenderer(
       int render_process_id,
       int render_frame_id);
 
-  base::RepeatingCallback<void(bool)> ar_module_installation_callback_;
-  base::RepeatingCallback<void(bool)> ar_core_installation_callback_;
-
   base::android::ScopedJavaGlobalRef<jobject> j_arcore_java_utils_;
 
   SurfaceReadyCallback surface_ready_callback_;
diff --git a/chrome/browser/android/vr/arcore_device/arcore_install_utils.h b/chrome/browser/android/vr/arcore_device/arcore_session_utils.h
similarity index 64%
rename from chrome/browser/android/vr/arcore_device/arcore_install_utils.h
rename to chrome/browser/android/vr/arcore_device/arcore_session_utils.h
index f115698..1573fbe 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_install_utils.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_session_utils.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 CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_INSTALL_UTILS_H_
-#define CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_INSTALL_UTILS_H_
+#ifndef CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_SESSION_UTILS_H_
+#define CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_SESSION_UTILS_H_
 
 #include "base/android/scoped_java_ref.h"
 #include "base/memory/weak_ptr.h"
@@ -30,18 +30,9 @@
     base::RepeatingCallback<void(bool touching, const gfx::PointF& location)>;
 using SurfaceDestroyedCallback = base::OnceClosure;
 
-class ArCoreInstallUtils {
+class ArCoreSessionUtils {
  public:
-  virtual ~ArCoreInstallUtils() = default;
-  // Returns true if AR module installation is supported, false otherwise.
-  virtual bool CanRequestInstallArModule() = 0;
-  // Returns true if AR module is not installed, false otherwise.
-  virtual bool ShouldRequestInstallArModule() = 0;
-  virtual void RequestInstallArModule(int render_process_id,
-                                      int render_frame_id) = 0;
-  virtual bool ShouldRequestInstallSupportedArCore() = 0;
-  virtual void RequestInstallSupportedArCore(int render_process_id,
-                                             int render_frame_id) = 0;
+  virtual ~ArCoreSessionUtils() = default;
   virtual bool EnsureLoaded() = 0;
   virtual base::android::ScopedJavaLocalRef<jobject>
   GetApplicationContext() = 0;
@@ -56,4 +47,4 @@
 
 }  // namespace vr
 
-#endif  // CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_INSTALL_UTILS_H_
+#endif  // CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_SESSION_UTILS_H_
diff --git a/chrome/browser/android/vr/vr_module_provider.cc b/chrome/browser/android/vr/vr_module_provider.cc
index 1004ae4..92abcf8 100644
--- a/chrome/browser/android/vr/vr_module_provider.cc
+++ b/chrome/browser/android/vr/vr_module_provider.cc
@@ -88,7 +88,7 @@
 static void JNI_VrModuleProvider_Init(JNIEnv* env) {
   GvrConsentHelper::SetInstance(std::make_unique<vr::GvrConsentHelperImpl>());
 #if BUILDFLAG(ENABLE_ARCORE)
-  ArcoreConsentPromptInterface::SetInstance(new ArcoreConsentPrompt());
+  ArCoreConsentPromptInterface::SetInstance(new ArCoreConsentPrompt());
 #endif
 }
 
diff --git a/chrome/browser/android/webapk/webapk.proto b/chrome/browser/android/webapk/webapk.proto
index 3afd167..e75cc63 100644
--- a/chrome/browser/android/webapk/webapk.proto
+++ b/chrome/browser/android/webapk/webapk.proto
@@ -46,6 +46,7 @@
     ORIENTATION_DIFFERS = 11;
     DISPLAY_MODE_DIFFERS = 12;
     WEB_SHARE_TARGET_DIFFERS = 13;
+    MANUALLY_TRIGGERED = 14;
   }
 
   // Package name of the WebAPK.
diff --git a/chrome/browser/android/webapk/webapk_info.cc b/chrome/browser/android/webapk/webapk_info.cc
index 9c77db2..f2d36b59 100644
--- a/chrome/browser/android/webapk/webapk_info.cc
+++ b/chrome/browser/android/webapk/webapk_info.cc
@@ -9,6 +9,7 @@
 WebApkInfo::WebApkInfo(std::string name,
                        std::string short_name,
                        std::string package_name,
+                       std::string id,
                        int shell_apk_version,
                        int version_code,
                        std::string uri,
@@ -20,10 +21,12 @@
                        base::Optional<SkColor> theme_color,
                        base::Optional<SkColor> background_color,
                        base::Time last_update_check_time,
-                       bool relax_updates)
+                       bool relax_updates,
+                       std::string update_status)
     : name(std::move(name)),
       short_name(std::move(short_name)),
       package_name(std::move(package_name)),
+      id(std::move(id)),
       shell_apk_version(shell_apk_version),
       version_code(version_code),
       uri(std::move(uri)),
@@ -35,7 +38,8 @@
       theme_color(theme_color),
       background_color(background_color),
       last_update_check_time(last_update_check_time),
-      relax_updates(relax_updates) {}
+      relax_updates(relax_updates),
+      update_status(std::move(update_status)) {}
 
 WebApkInfo::~WebApkInfo() {}
 
diff --git a/chrome/browser/android/webapk/webapk_info.h b/chrome/browser/android/webapk/webapk_info.h
index d2d8fb0..d035bb3 100644
--- a/chrome/browser/android/webapk/webapk_info.h
+++ b/chrome/browser/android/webapk/webapk_info.h
@@ -23,6 +23,7 @@
   WebApkInfo(std::string name,
              std::string short_name,
              std::string package_name,
+             std::string id,
              int shell_apk_version,
              int version_code,
              std::string uri,
@@ -34,7 +35,8 @@
              base::Optional<SkColor> theme_color,
              base::Optional<SkColor> background_color,
              base::Time last_update_check_time,
-             bool relax_updates);
+             bool relax_updates,
+             std::string update_status);
   ~WebApkInfo();
 
   WebApkInfo& operator=(WebApkInfo&& other) noexcept;
@@ -49,6 +51,9 @@
   // Package name of the WebAPK.
   std::string package_name;
 
+  // Internal ID of the WebAPK.
+  std::string id;
+
   // Shell APK version of the WebAPK.
   int shell_apk_version;
 
@@ -66,6 +71,9 @@
   base::Time last_update_check_time;
   bool relax_updates;
 
+  // Update Status of the WebAPK.
+  std::string update_status;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(WebApkInfo);
 };
diff --git a/chrome/browser/android/webapk/webapk_installer.cc b/chrome/browser/android/webapk/webapk_installer.cc
index 004c369..f27f3e5 100644
--- a/chrome/browser/android/webapk/webapk_installer.cc
+++ b/chrome/browser/android/webapk/webapk_installer.cc
@@ -140,6 +140,8 @@
       return webapk::WebApk::DISPLAY_MODE_DIFFERS;
     case WebApkUpdateReason::WEB_SHARE_TARGET_DIFFERS:
       return webapk::WebApk::WEB_SHARE_TARGET_DIFFERS;
+    case WebApkUpdateReason::MANUALLY_TRIGGERED:
+      return webapk::WebApk::MANUALLY_TRIGGERED;
   }
 }
 
diff --git a/chrome/browser/android/webapk/webapk_types.h b/chrome/browser/android/webapk/webapk_types.h
index 5269faf..73d202f 100644
--- a/chrome/browser/android/webapk/webapk_types.h
+++ b/chrome/browser/android/webapk/webapk_types.h
@@ -23,6 +23,7 @@
   ORIENTATION_DIFFERS,
   DISPLAY_MODE_DIFFERS,
   WEB_SHARE_TARGET_DIFFERS,
+  MANUALLY_TRIGGERED,
 };
 
 #endif  // CHROME_BROWSER_ANDROID_WEBAPK_WEBAPK_TYPES_H_
diff --git a/chrome/browser/autofill/address_accessory_controller_impl.cc b/chrome/browser/autofill/address_accessory_controller_impl.cc
index debb8e0..72aff54 100644
--- a/chrome/browser/autofill/address_accessory_controller_impl.cc
+++ b/chrome/browser/autofill/address_accessory_controller_impl.cc
@@ -27,10 +27,7 @@
 // Defines which types to load from the Personal data manager and add as field
 // to the address sheet. Order matters.
 constexpr ServerFieldType kTypesToInclude[] = {
-    // TODO(crbug.com/965494): Possibly, the names should be in a single chip.
-    ServerFieldType::NAME_FIRST,
-    ServerFieldType::NAME_MIDDLE,
-    ServerFieldType::NAME_LAST,
+    ServerFieldType::NAME_FULL,
     ServerFieldType::COMPANY_NAME,
     ServerFieldType::ADDRESS_HOME_LINE1,
     ServerFieldType::ADDRESS_HOME_LINE2,
diff --git a/chrome/browser/autofill/address_accessory_controller_impl_unittest.cc b/chrome/browser/autofill/address_accessory_controller_impl_unittest.cc
index bbf7e26..b13d681 100644
--- a/chrome/browser/autofill/address_accessory_controller_impl_unittest.cc
+++ b/chrome/browser/autofill/address_accessory_controller_impl_unittest.cc
@@ -114,9 +114,7 @@
       result,
       AddressAccessorySheetDataBuilder(base::string16())
           .AddUserInfo()
-          .AppendSimpleField(canadian.GetRawInfo(ServerFieldType::NAME_FIRST))
-          .AppendSimpleField(canadian.GetRawInfo(ServerFieldType::NAME_MIDDLE))
-          .AppendSimpleField(canadian.GetRawInfo(ServerFieldType::NAME_LAST))
+          .AppendSimpleField(canadian.GetRawInfo(ServerFieldType::NAME_FULL))
           .AppendSimpleField(canadian.GetRawInfo(ServerFieldType::COMPANY_NAME))
           .AppendSimpleField(
               canadian.GetRawInfo(ServerFieldType::ADDRESS_HOME_LINE1))
@@ -163,11 +161,7 @@
   personal_data_manager()->AddProfile(email);
   ASSERT_EQ(result, AddressAccessorySheetDataBuilder(base::string16())
                         .AddUserInfo()
-                        /*name first:*/
-                        .AppendSimpleField(base::string16())
-                        /*name middle:*/
-                        .AppendSimpleField(base::string16())
-                        /*name last:*/
+                        /*name full:*/
                         .AppendSimpleField(base::string16())
                         /*company name:*/
                         .AppendSimpleField(base::string16())
diff --git a/chrome/browser/autofill/autofill_keyboard_accessory_adapter.cc b/chrome/browser/autofill/autofill_keyboard_accessory_adapter.cc
index 4caa854..339addef 100644
--- a/chrome/browser/autofill/autofill_keyboard_accessory_adapter.cc
+++ b/chrome/browser/autofill/autofill_keyboard_accessory_adapter.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
+#include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/autofill/autofill_popup_controller.h"
 #include "chrome/browser/ui/autofill/autofill_popup_layout_model.h"
 #include "components/autofill/core/browser/ui/popup_item_ids.h"
@@ -17,6 +18,22 @@
 
 namespace autofill {
 
+constexpr base::char16 kLabelSeparator = ' ';
+constexpr size_t kMaxBulletCount = 8;
+
+namespace {
+base::string16 CreateLabel(const Suggestion& suggestion) {
+  base::string16 password =
+      suggestion.additional_label.substr(0, kMaxBulletCount);
+  // The label contains the signon_realm or is empty. The additional_label can
+  // never be empty since it must contain a password.
+  if (suggestion.label.empty())
+    return password;
+  return suggestion.label + kLabelSeparator + password;
+}
+
+}  // namespace
+
 AutofillKeyboardAccessoryAdapter::AutofillKeyboardAccessoryAdapter(
     AutofillPopupController* controller,
     unsigned int animation_duration_millis,
@@ -49,12 +66,15 @@
   DCHECK(controller_) << "Call OnSuggestionsChanged only from its owner!";
   DCHECK(view_) << "OnSuggestionsChanged called before a View was set!";
 
+  labels_.clear();
   front_element_ = base::nullopt;
   for (int i = 0; i < GetLineCount(); ++i) {
     const Suggestion& suggestion = controller_->GetSuggestionAt(i);
     if (suggestion.frontend_id != POPUP_ITEM_ID_CLEAR_FORM &&
-        suggestion.frontend_id != POPUP_ITEM_ID_CREATE_HINT)
+        suggestion.frontend_id != POPUP_ITEM_ID_CREATE_HINT) {
+      labels_.push_back(CreateLabel(suggestion));
       continue;
+    }
     DCHECK(!front_element_.has_value()) << "Additional front item at: " << i;
     front_element_ = base::Optional<int>(i);
   }
@@ -88,11 +108,7 @@
 const base::string16& AutofillKeyboardAccessoryAdapter::GetElidedLabelAt(
     int row) const {
   DCHECK(controller_) << "Call OnSuggestionsChanged only from its owner!";
-  const base::string16& label =
-      controller_->GetElidedLabelAt(OffsetIndexFor(row));
-  if (label.empty())
-    return GetSuggestionAt(row).additional_label;
-  return label;
+  return labels_[row];
 }
 
 bool AutofillKeyboardAccessoryAdapter::GetRemovalConfirmationText(
diff --git a/chrome/browser/autofill/autofill_keyboard_accessory_adapter.h b/chrome/browser/autofill/autofill_keyboard_accessory_adapter.h
index 8ad5806..c8b597d 100644
--- a/chrome/browser/autofill/autofill_keyboard_accessory_adapter.h
+++ b/chrome/browser/autofill/autofill_keyboard_accessory_adapter.h
@@ -100,6 +100,9 @@
   AutofillPopupController* controller_;  // weak.
   std::unique_ptr<AutofillKeyboardAccessoryAdapter::AccessoryView> view_;
 
+  // The labels to be used for the input chips.
+  std::vector<base::string16> labels_;
+
   // If 0, don't animate suggestion view.
   const unsigned int animation_duration_millis_;
 
diff --git a/chrome/browser/autofill/autofill_keyboard_accessory_adapter_unittest.cc b/chrome/browser/autofill/autofill_keyboard_accessory_adapter_unittest.cc
index 12ef283..cb78884 100644
--- a/chrome/browser/autofill/autofill_keyboard_accessory_adapter_unittest.cc
+++ b/chrome/browser/autofill/autofill_keyboard_accessory_adapter_unittest.cc
@@ -5,6 +5,7 @@
 #include <cstddef>
 
 #include <memory>
+#include <string>
 #include <utility>
 #include <vector>
 
@@ -51,13 +52,20 @@
   DISALLOW_COPY_AND_ASSIGN(MockAccessoryView);
 };
 
+Suggestion createPasswordEntry(std::string password,
+                               std::string username,
+                               std::string psl_origin) {
+  Suggestion s(/*value=*/username, /*label=*/psl_origin, /*icon=*/"",
+               PopupItemId::POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY);
+  s.additional_label = ASCIIToUTF16(password);
+  return s;
+}
+
 std::vector<Suggestion> createSuggestions() {
   std::vector<Suggestion> suggestions = {
-      Suggestion("*", "A", "", PopupItemId::POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY),
-      Suggestion("**", "", "", PopupItemId::POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY),
-      Suggestion("***", "C", "",
-                 PopupItemId::POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY)};
-  suggestions[1].additional_label = ASCIIToUTF16("B");
+      createPasswordEntry("****************", "Alf", ""),
+      createPasswordEntry("****************", "Berta", "psl.origin.eg"),
+      createPasswordEntry("***", "Carl", "")};
   return suggestions;
 }
 
@@ -174,14 +182,19 @@
 }
 
 TEST_F(AutofillKeyboardAccessoryAdapterTest, UseAdditionalLabelForElidedLabel) {
-  controller()->set_suggestions(createSuggestions());
+  controller()->set_suggestions(createSuggestions(/*clearItemOffset=*/1));
   NotifyAboutSuggestions();
 
-  // If there is a label, use it.
-  EXPECT_EQ(adapter_as_controller()->GetElidedLabelAt(0), ASCIIToUTF16("A"));
+  // If there is a label, use it but cap at 8 bullets.
+  EXPECT_EQ(adapter_as_controller()->GetElidedLabelAt(0),
+            ASCIIToUTF16("********"));
 
   // If the label is empty, use the additional label:
-  EXPECT_EQ(adapter_as_controller()->GetElidedLabelAt(1), ASCIIToUTF16("B"));
+  EXPECT_EQ(adapter_as_controller()->GetElidedLabelAt(1),
+            ASCIIToUTF16("psl.origin.eg ********"));
+
+  // If the password has less than 8 bullets, show the exact amount.
+  EXPECT_EQ(adapter_as_controller()->GetElidedLabelAt(2), ASCIIToUTF16("***"));
 }
 
 TEST_F(AutofillKeyboardAccessoryAdapterTest, ProvideReorderedSuggestions) {
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index a8017edc..68ff588 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3515,6 +3515,9 @@
     web_prefs->text_track_text_shadow = style->text_shadow;
     web_prefs->text_track_font_family = style->font_family;
     web_prefs->text_track_font_variant = style->font_variant;
+    web_prefs->text_track_window_color = style->window_color;
+    web_prefs->text_track_window_padding = style->window_padding;
+    web_prefs->text_track_window_radius = style->window_radius;
   }
 
   for (size_t i = 0; i < extra_parts_.size(); ++i)
diff --git a/chrome/browser/chrome_notification_types.h b/chrome/browser/chrome_notification_types.h
index 9176955..763f2fa 100644
--- a/chrome/browser/chrome_notification_types.h
+++ b/chrome/browser/chrome_notification_types.h
@@ -118,14 +118,6 @@
   // the WebContents containing the NavigationController is destroyed.
   NOTIFICATION_TAB_CLOSING,
 
-  // Stuff inside the tabs ---------------------------------------------------
-
-  // This notification is sent when the result of a find-in-page search is
-  // available with the browser process. The source is a Source<WebContents>.
-  // Details encompass a FindNotificationDetail object that tells whether the
-  // match was found or not found.
-  NOTIFICATION_FIND_RESULT_AVAILABLE,
-
   // Authentication ----------------------------------------------------------
 
   // This is sent when a login prompt is shown.  The source is the
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 1ccbe0a..104e5ff 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -736,6 +736,7 @@
     "crostini/crostini_remover.h",
     "crostini/crostini_reporting_util.cc",
     "crostini/crostini_reporting_util.h",
+    "crostini/crostini_simple_types.h",
     "crostini/crostini_terminal.cc",
     "crostini/crostini_terminal.h",
     "crostini/crostini_util.cc",
@@ -1256,12 +1257,12 @@
     "login/reauth_stats.h",
     "login/saml/in_session_password_change_manager.cc",
     "login/saml/in_session_password_change_manager.h",
+    "login/saml/password_expiry_notification.cc",
+    "login/saml/password_expiry_notification.h",
     "login/saml/saml_offline_signin_limiter.cc",
     "login/saml/saml_offline_signin_limiter.h",
     "login/saml/saml_offline_signin_limiter_factory.cc",
     "login/saml/saml_offline_signin_limiter_factory.h",
-    "login/saml/saml_password_expiry_notification.cc",
-    "login/saml/saml_password_expiry_notification.h",
     "login/saml/saml_profile_prefs.cc",
     "login/saml/saml_profile_prefs.h",
     "login/screen_manager.cc",
@@ -2514,8 +2515,9 @@
     "login/quick_unlock/fingerprint_storage_unittest.cc",
     "login/quick_unlock/pin_storage_prefs_unittest.cc",
     "login/quick_unlock/quick_unlock_storage_unittest.cc",
+    "login/saml/in_session_password_change_manager_unittest.cc",
+    "login/saml/password_expiry_notification_unittest.cc",
     "login/saml/saml_offline_signin_limiter_unittest.cc",
-    "login/saml/saml_password_expiry_notification_unittest.cc",
     "login/screens/multidevice_setup_screen_unittest.cc",
     "login/screens/network_screen_unittest.cc",
     "login/screens/recommend_apps/recommend_apps_fetcher_impl_unittest.cc",
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.h b/chrome/browser/chromeos/crostini/crostini_manager.h
index 81f1716c..18d5739 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.h
+++ b/chrome/browser/chromeos/crostini/crostini_manager.h
@@ -15,6 +15,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/optional.h"
+#include "chrome/browser/chromeos/crostini/crostini_simple_types.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/component_updater/cros_component_installer_chromeos.h"
 #include "chrome/browser/ui/browser.h"
@@ -29,137 +30,6 @@
 
 namespace crostini {
 
-// Result types for various callbacks etc.
-
-// WARNING: Do not remove or re-order these values, as they are used in user
-// visible error messages and logs. New entries should only be added to the end.
-// This message was added during development of M74, error codes from prior
-// versions may differ from the numbering here.
-enum class CrostiniResult {
-  SUCCESS = 0,
-  // DBUS_ERROR = 1,
-  // UNPARSEABLE_RESPONSE = 2,
-  // INSUFFICIENT_DISK = 3,
-  CREATE_DISK_IMAGE_FAILED = 4,
-  VM_START_FAILED = 5,
-  VM_STOP_FAILED = 6,
-  DESTROY_DISK_IMAGE_FAILED = 7,
-  LIST_VM_DISKS_FAILED = 8,
-  CLIENT_ERROR = 9,
-  // DISK_TYPE_ERROR = 10,
-  CONTAINER_DOWNLOAD_TIMED_OUT = 11,
-  CONTAINER_CREATE_CANCELLED = 12,
-  CONTAINER_CREATE_FAILED = 13,
-  CONTAINER_START_CANCELLED = 14,
-  CONTAINER_START_FAILED = 15,
-  // LAUNCH_CONTAINER_APPLICATION_FAILED = 16,
-  INSTALL_LINUX_PACKAGE_FAILED = 17,
-  BLOCKING_OPERATION_ALREADY_ACTIVE = 18,
-  UNINSTALL_PACKAGE_FAILED = 19,
-  // SSHFS_MOUNT_ERROR = 20,
-  OFFLINE_WHEN_UPGRADE_REQUIRED = 21,
-  LOAD_COMPONENT_FAILED = 22,
-  // PERMISSION_BROKER_ERROR = 23,
-  // ATTACH_USB_FAILED = 24,
-  // DETACH_USB_FAILED = 25,
-  // LIST_USB_FAILED = 26,
-  CROSTINI_UNINSTALLER_RUNNING = 27,
-  // UNKNOWN_USB_DEVICE = 28,
-  UNKNOWN_ERROR = 29,
-  CONTAINER_EXPORT_IMPORT_FAILED = 30,
-  CONTAINER_EXPORT_IMPORT_FAILED_VM_STOPPED = 31,
-  CONTAINER_EXPORT_IMPORT_FAILED_VM_STARTED = 32,
-  CONTAINER_EXPORT_IMPORT_FAILED_ARCHITECTURE = 33,
-  NOT_ALLOWED = 34,
-  CONTAINER_EXPORT_IMPORT_FAILED_SPACE = 35,
-  GET_CONTAINER_SSH_KEYS_FAILED = 36,
-};
-
-enum class InstallLinuxPackageProgressStatus {
-  SUCCEEDED,
-  FAILED,
-  DOWNLOADING,
-  INSTALLING,
-};
-
-enum class VmState {
-  STARTING,
-  STARTED,
-  STOPPING,
-};
-
-enum class UninstallPackageProgressStatus {
-  SUCCEEDED,
-  FAILED,
-  UNINSTALLING,  // In progress
-};
-
-// TODO(juwa): delete this once the new version of tremplin has shipped.
-enum class ExportContainerProgressStatus {
-  // Deprecated. Has been replaced by STREAMING.
-  PACK,
-  // Deprecated. Has been replaced by STREAMING.
-  DOWNLOAD,
-  STREAMING,
-};
-
-enum class ImportContainerProgressStatus {
-  UPLOAD,
-  UNPACK,
-  FAILURE_ARCHITECTURE,
-  FAILURE_SPACE,
-};
-
-struct VmInfo {
-  VmState state;
-  vm_tools::concierge::VmInfo info;
-};
-
-struct StreamingExportStatus {
-  uint32_t total_files;
-  uint64_t total_bytes;
-  uint32_t exported_files;
-  uint64_t exported_bytes;
-};
-
-struct ContainerInfo {
-  ContainerInfo(std::string name, std::string username, std::string homedir);
-  ~ContainerInfo();
-  ContainerInfo(const ContainerInfo&);
-
-  std::string name;
-  std::string username;
-  base::FilePath homedir;
-  bool sshfs_mounted = false;
-};
-
-// Return type when getting app icons from within a container.
-struct Icon {
-  std::string desktop_file_id;
-
-  // Icon file content in PNG format.
-  std::string content;
-};
-
-struct LinuxPackageInfo {
-  LinuxPackageInfo();
-  LinuxPackageInfo(const LinuxPackageInfo&);
-  ~LinuxPackageInfo();
-
-  bool success;
-
-  // A textual reason for the failure, only set when success is false.
-  std::string failure_reason;
-
-  // The remaining fields are only set when success is true.
-  // package_id is given as "name;version;arch;data".
-  std::string package_id;
-  std::string name;
-  std::string version;
-  std::string summary;
-  std::string description;
-};
-
 class LinuxPackageOperationProgressObserver {
  public:
   // A successfully started package install will continually fire progress
diff --git a/chrome/browser/chromeos/crostini/crostini_registry_service.cc b/chrome/browser/chromeos/crostini/crostini_registry_service.cc
index e017ce1..3e389a7 100644
--- a/chrome/browser/chromeos/crostini/crostini_registry_service.cc
+++ b/chrome/browser/chromeos/crostini/crostini_registry_service.cc
@@ -18,6 +18,7 @@
 #include "base/time/default_clock.h"
 #include "base/time/time.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/crostini/crostini_manager.h"
 #include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/profiles/profile.h"
diff --git a/chrome/browser/chromeos/crostini/crostini_registry_service.h b/chrome/browser/chromeos/crostini/crostini_registry_service.h
index 4f4c2e8..6211d8a2 100644
--- a/chrome/browser/chromeos/crostini/crostini_registry_service.h
+++ b/chrome/browser/chromeos/crostini/crostini_registry_service.h
@@ -15,7 +15,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/values.h"
-#include "chrome/browser/chromeos/crostini/crostini_manager.h"
+#include "chrome/browser/chromeos/crostini/crostini_simple_types.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "ui/base/resource/scale_factor.h"
 
diff --git a/chrome/browser/chromeos/crostini/crostini_simple_types.h b/chrome/browser/chromeos/crostini/crostini_simple_types.h
new file mode 100644
index 0000000..b496414
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_simple_types.h
@@ -0,0 +1,152 @@
+// 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.
+
+#ifndef CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_SIMPLE_TYPES_H_
+#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_SIMPLE_TYPES_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "chromeos/dbus/concierge/service.pb.h"
+
+// This file contains simple C++ types (enums and Plain-Old-Data structs).
+// Importantly, #include'ing this file will not depend on eventually executing
+// "#include <dbus/dbus.h>",
+
+namespace crostini {
+
+// Result types for various callbacks etc.
+
+// WARNING: Do not remove or re-order these values, as they are used in user
+// visible error messages and logs. New entries should only be added to the end.
+// This message was added during development of M74, error codes from prior
+// versions may differ from the numbering here.
+enum class CrostiniResult {
+  SUCCESS = 0,
+  // DBUS_ERROR = 1,
+  // UNPARSEABLE_RESPONSE = 2,
+  // INSUFFICIENT_DISK = 3,
+  CREATE_DISK_IMAGE_FAILED = 4,
+  VM_START_FAILED = 5,
+  VM_STOP_FAILED = 6,
+  DESTROY_DISK_IMAGE_FAILED = 7,
+  LIST_VM_DISKS_FAILED = 8,
+  CLIENT_ERROR = 9,
+  // DISK_TYPE_ERROR = 10,
+  CONTAINER_DOWNLOAD_TIMED_OUT = 11,
+  CONTAINER_CREATE_CANCELLED = 12,
+  CONTAINER_CREATE_FAILED = 13,
+  CONTAINER_START_CANCELLED = 14,
+  CONTAINER_START_FAILED = 15,
+  // LAUNCH_CONTAINER_APPLICATION_FAILED = 16,
+  INSTALL_LINUX_PACKAGE_FAILED = 17,
+  BLOCKING_OPERATION_ALREADY_ACTIVE = 18,
+  UNINSTALL_PACKAGE_FAILED = 19,
+  // SSHFS_MOUNT_ERROR = 20,
+  OFFLINE_WHEN_UPGRADE_REQUIRED = 21,
+  LOAD_COMPONENT_FAILED = 22,
+  // PERMISSION_BROKER_ERROR = 23,
+  // ATTACH_USB_FAILED = 24,
+  // DETACH_USB_FAILED = 25,
+  // LIST_USB_FAILED = 26,
+  CROSTINI_UNINSTALLER_RUNNING = 27,
+  // UNKNOWN_USB_DEVICE = 28,
+  UNKNOWN_ERROR = 29,
+  CONTAINER_EXPORT_IMPORT_FAILED = 30,
+  CONTAINER_EXPORT_IMPORT_FAILED_VM_STOPPED = 31,
+  CONTAINER_EXPORT_IMPORT_FAILED_VM_STARTED = 32,
+  CONTAINER_EXPORT_IMPORT_FAILED_ARCHITECTURE = 33,
+  NOT_ALLOWED = 34,
+  CONTAINER_EXPORT_IMPORT_FAILED_SPACE = 35,
+  GET_CONTAINER_SSH_KEYS_FAILED = 36,
+};
+
+enum class InstallLinuxPackageProgressStatus {
+  SUCCEEDED,
+  FAILED,
+  DOWNLOADING,
+  INSTALLING,
+};
+
+enum class VmState {
+  STARTING,
+  STARTED,
+  STOPPING,
+};
+
+enum class UninstallPackageProgressStatus {
+  SUCCEEDED,
+  FAILED,
+  UNINSTALLING,  // In progress
+};
+
+// TODO(juwa): delete this once the new version of tremplin has shipped.
+enum class ExportContainerProgressStatus {
+  // Deprecated. Has been replaced by STREAMING.
+  PACK,
+  // Deprecated. Has been replaced by STREAMING.
+  DOWNLOAD,
+  STREAMING,
+};
+
+enum class ImportContainerProgressStatus {
+  UPLOAD,
+  UNPACK,
+  FAILURE_ARCHITECTURE,
+  FAILURE_SPACE,
+};
+
+struct VmInfo {
+  VmState state;
+  vm_tools::concierge::VmInfo info;
+};
+
+struct StreamingExportStatus {
+  uint32_t total_files;
+  uint64_t total_bytes;
+  uint32_t exported_files;
+  uint64_t exported_bytes;
+};
+
+struct ContainerInfo {
+  ContainerInfo(std::string name, std::string username, std::string homedir);
+  ~ContainerInfo();
+  ContainerInfo(const ContainerInfo&);
+
+  std::string name;
+  std::string username;
+  base::FilePath homedir;
+  bool sshfs_mounted = false;
+};
+
+// Return type when getting app icons from within a container.
+struct Icon {
+  std::string desktop_file_id;
+
+  // Icon file content in PNG format.
+  std::string content;
+};
+
+struct LinuxPackageInfo {
+  LinuxPackageInfo();
+  LinuxPackageInfo(const LinuxPackageInfo&);
+  ~LinuxPackageInfo();
+
+  bool success;
+
+  // A textual reason for the failure, only set when success is false.
+  std::string failure_reason;
+
+  // The remaining fields are only set when success is true.
+  // package_id is given as "name;version;arch;data".
+  std::string package_id;
+  std::string name;
+  std::string version;
+  std::string summary;
+  std::string description;
+};
+
+}  // namespace crostini
+
+#endif  // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_SIMPLE_TYPES_H_
diff --git a/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc b/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc
index 9a8ea1f..51a3fb4 100644
--- a/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc
+++ b/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/chrome_features.h"
+#include "components/arc/arc_features.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
@@ -106,7 +107,10 @@
     dbus::MethodCall* method_call,
     dbus::ExportedObject::ResponseSender response_sender) {
   static const base::Feature constexpr* kFeatureLookup[] = {
-      &features::kUsbbouncer, &features::kUsbguard};
+      &features::kUsbbouncer, &features::kUsbguard,
+      &arc::kNativeBridgeExperimentFeature, &arc::kFilePickerExperimentFeature,
+      &arc::kCustomTabsExperimentFeature, &arc::kPrintSpoolerExperimentFeature,
+  };
 
   dbus::MessageReader reader(method_call);
   std::string feature_name;
diff --git a/chrome/browser/chromeos/login/saml/in_session_password_change_manager.cc b/chrome/browser/chromeos/login/saml/in_session_password_change_manager.cc
index 23602b9..62bd7de 100644
--- a/chrome/browser/chromeos/login/saml/in_session_password_change_manager.cc
+++ b/chrome/browser/chromeos/login/saml/in_session_password_change_manager.cc
@@ -4,24 +4,85 @@
 
 #include "chrome/browser/chromeos/login/saml/in_session_password_change_manager.h"
 
+#include "ash/public/cpp/session/session_activation_observer.h"
+#include "ash/public/cpp/session/session_controller.h"
+#include "base/feature_list.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_platform_part_chromeos.h"
 #include "chrome/browser/chromeos/login/auth/chrome_cryptohome_authenticator.h"
-#include "chrome/browser/chromeos/login/saml/saml_password_expiry_notification.h"
+#include "chrome/browser/chromeos/login/saml/password_expiry_notification.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/bro