diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 924a943d..c96f62da 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2932,6 +2932,8 @@
       "android/preferences/autofill/autofill_profile_bridge.h",
       "android/preferences/browser_prefs_android.cc",
       "android/preferences/browser_prefs_android.h",
+      "android/preferences/clipboard_android.cc",
+      "android/preferences/clipboard_android.h",
       "android/preferences/pref_service_bridge.cc",
       "android/preferences/pref_service_bridge.h",
       "android/preferences/preferences_launcher.cc",
diff --git a/chrome/browser/android/preferences/browser_prefs_android.cc b/chrome/browser/android/preferences/browser_prefs_android.cc
index 6e9a9c7..1db741d 100644
--- a/chrome/browser/android/preferences/browser_prefs_android.cc
+++ b/chrome/browser/android/preferences/browser_prefs_android.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/android/preferences/browser_prefs_android.h"
 
+#include "chrome/browser/android/preferences/clipboard_android.h"
 #include "chrome/browser/android/search_geolocation/search_geolocation_disclosure_tab_helper.h"
 #include "chrome/browser/android/search_geolocation/search_geolocation_service.h"
 #include "chrome/browser/notifications/notification_platform_bridge_android.h"
@@ -12,6 +13,10 @@
 
 namespace android {
 
+void RegisterPrefs(PrefRegistrySimple* registry) {
+  RegisterClipboardAndroidPrefs(registry);
+}
+
 void RegisterUserProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
   NotificationPlatformBridgeAndroid::RegisterProfilePrefs(registry);
   SearchGeolocationDisclosureTabHelper::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/android/preferences/browser_prefs_android.h b/chrome/browser/android/preferences/browser_prefs_android.h
index 8693362..0764c5a 100644
--- a/chrome/browser/android/preferences/browser_prefs_android.h
+++ b/chrome/browser/android/preferences/browser_prefs_android.h
@@ -5,12 +5,17 @@
 #ifndef CHROME_BROWSER_ANDROID_PREFERENCES_BROWSER_PREFS_ANDROID_H_
 #define CHROME_BROWSER_ANDROID_PREFERENCES_BROWSER_PREFS_ANDROID_H_
 
+class PrefRegistrySimple;
+
 namespace user_prefs {
 class PrefRegistrySyncable;
 }  // namespace user_prefs
 
 namespace android {
 
+// Register all prefs that will be used via the local state PrefService.
+void RegisterPrefs(PrefRegistrySimple* registry);
+
 // Register all prefs that will be used via a PrefService attached to a user
 // Profile on Android.
 void RegisterUserProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
diff --git a/chrome/browser/android/preferences/clipboard_android.cc b/chrome/browser/android/preferences/clipboard_android.cc
new file mode 100644
index 0000000..9e5882f8
--- /dev/null
+++ b/chrome/browser/android/preferences/clipboard_android.cc
@@ -0,0 +1,43 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/preferences/clipboard_android.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/time/time.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/clipboard_android.h"
+
+namespace {
+
+void HandleClipboardModified(PrefService* local_state, base::Time time) {
+  local_state->SetInt64(prefs::kClipboardLastModifiedTime,
+                        time.ToInternalValue());
+}
+
+}  // namespace
+
+namespace android {
+
+void RegisterClipboardAndroidPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterInt64Pref(prefs::kClipboardLastModifiedTime, 0u);
+}
+
+void InitClipboardAndroidFromLocalState(PrefService* local_state) {
+  DCHECK(local_state);
+  // Given the context, the cast is guaranteed to succeed.
+  ui::ClipboardAndroid* clipboard =
+      static_cast<ui::ClipboardAndroid*>(ui::Clipboard::GetForCurrentThread());
+  clipboard->SetLastModifiedTimeWithoutRunningCallback(
+      base::Time::FromInternalValue(
+          local_state->GetInt64(prefs::kClipboardLastModifiedTime)));
+  clipboard->SetModifiedCallback(
+      base::Bind(&HandleClipboardModified, local_state));
+}
+
+}  // namespace android
diff --git a/chrome/browser/android/preferences/clipboard_android.h b/chrome/browser/android/preferences/clipboard_android.h
new file mode 100644
index 0000000..eed04f47
--- /dev/null
+++ b/chrome/browser/android/preferences/clipboard_android.h
@@ -0,0 +1,19 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ANDROID_PREFERENCES_CLIPBOARD_ANDROID_H_
+#define CHROME_BROWSER_ANDROID_PREFERENCES_CLIPBOARD_ANDROID_H_
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace android {
+
+void RegisterClipboardAndroidPrefs(PrefRegistrySimple* registry);
+
+void InitClipboardAndroidFromLocalState(PrefService* local_state);
+
+}  // namespace android
+
+#endif  // CHROME_BROWSER_ANDROID_PREFERENCES_CLIPBOARD_ANDROID_H_
diff --git a/chrome/browser/chrome_browser_main_android.cc b/chrome/browser/chrome_browser_main_android.cc
index 6617e4f..a788005 100644
--- a/chrome/browser/chrome_browser_main_android.cc
+++ b/chrome/browser/chrome_browser_main_android.cc
@@ -12,7 +12,9 @@
 #include "base/task_scheduler/post_task.h"
 #include "base/trace_event/trace_event.h"
 #include "chrome/browser/android/mojo/chrome_interface_registrar_android.h"
+#include "chrome/browser/android/preferences/clipboard_android.h"
 #include "chrome/browser/android/seccomp_support_detector.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/signin/signin_manager_factory.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/descriptors_android.h"
@@ -97,6 +99,13 @@
       content::BrowserThread::FILE, FROM_HERE,
       base::Bind(&DeleteFileTask, bookmark_image_file_path),
       base::TimeDelta::FromMinutes(1));
+
+  // Idempotent.  Needs to be called once on startup.  If
+  // InitializeClipboardAndroidFromLocalState() is called multiple times (e.g.,
+  // once per profile load), that's okay; the additional calls don't change
+  // anything.
+  android::InitClipboardAndroidFromLocalState(g_browser_process->local_state());
+
   // Start watching the preferences that need to be backed up backup using
   // Android backup, so that we create a new backup if they change.
   backup_watcher_.reset(new chrome::android::ChromeBackupWatcher(profile()));
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 9c5edf8f..e1b39c36 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -377,6 +377,10 @@
   PluginsResourceService::RegisterPrefs(registry);
 #endif
 
+#if defined(OS_ANDROID)
+  ::android::RegisterPrefs(registry);
+#endif
+
 #if !defined(OS_ANDROID)
   task_manager::TaskManagerInterface::RegisterPrefs(registry);
 #endif  // !defined(OS_ANDROID)
diff --git a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
index c8c7fad..a8c8bff 100644
--- a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
@@ -562,26 +562,13 @@
     if (!rfh)
       return VISIBILITY_ERROR;
 
-    // clang-format off
-    std::string jsFindVisibility = R"(
-      (function isNodeVisible(node) {
-        if (!node) return 'node not found';
-        if (node.offsetWidth === 0 || node.offsetHeight === 0) return false;
-        // Don't check opacity, since the css transition may actually leave
-        // opacity at 0 after it's been unhidden
-        if (node.classList.contains('hidden')) return false;
-        // Otherwise, we must check all parent nodes
-        var parentVisibility = isNodeVisible(node.parentElement);
-        if (parentVisibility === 'node not found') {
-          return true; // none of the parents are set invisible
-        }
-        return parentVisibility;
-      }(document.getElementById(')" + node_id + R"(')));)";
-    // clang-format on
-
-    std::unique_ptr<base::Value> value =
-        content::ExecuteScriptAndGetValue(rfh, jsFindVisibility);
-
+    std::unique_ptr<base::Value> value = content::ExecuteScriptAndGetValue(
+        rfh, "var node = document.getElementById('" + node_id +
+                 "');\n"
+                 "if (node)\n"
+                 "   node.offsetWidth > 0 && node.offsetHeight > 0;"
+                 "else\n"
+                 "  'node not found';\n");
     if (!value.get())
       return VISIBILITY_ERROR;
 
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index a8a5cec..6786c82 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -2534,4 +2534,11 @@
 const char kSettingsResetPromptLastTriggeredForHomepage[] =
     "settings_reset_prompt.last_triggered_for_homepage";
 
+#if defined(OS_ANDROID)
+// Timestamp of the clipboard's last modified time, stored in base::Time's
+// internal format (int64) in local store.  (I.e., this is not a per-profile
+// pref.)
+const char kClipboardLastModifiedTime[] = "ui.clipboard.last_modified_time";
+#endif
+
 }  // namespace prefs
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 44f3d46..04c8bf16 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -912,6 +912,11 @@
 extern const char kSettingsResetPromptLastTriggeredForDefaultSearch[];
 extern const char kSettingsResetPromptLastTriggeredForStartupUrls[];
 extern const char kSettingsResetPromptLastTriggeredForHomepage[];
+
+#if defined(OS_ANDROID)
+extern const char kClipboardLastModifiedTime[];
+#endif
+
 }  // namespace prefs
 
 #endif  // CHROME_COMMON_PREF_NAMES_H_
diff --git a/components/security_interstitials/core/browser/resources/interstitial_v2.css b/components/security_interstitials/core/browser/resources/interstitial_v2.css
index d82e6bd..ac09eece 100644
--- a/components/security_interstitials/core/browser/resources/interstitial_v2.css
+++ b/components/security_interstitials/core/browser/resources/interstitial_v2.css
@@ -328,10 +328,10 @@
  * Details message replaces the top content in its own scrollable area.
  */
 
-@media (max-width: 420px) {
+@media (max-width: 420px) and (max-height: 736px) and (orientation: portrait) {
   #details-button {
     border: 0;
-    margin: 28px 0 0;
+    margin: 8px 0 0;
   }
 
   .secondary-button {
@@ -342,17 +342,18 @@
 
 /* Fixed nav. */
 @media (min-width: 240px) and (max-width: 420px) and
-       (min-height: 401px),
-       (min-width: 421px) and (min-height: 240px) and
-       (max-height: 736px) {
+       (min-height: 401px) and (max-height: 736px) and (orientation:portrait),
+       (min-width: 421px) and (max-width: 736px) and (min-height: 240px) and
+       (max-height: 420px) and (orientation:landscape) {
   body .nav-wrapper {
     background: #f7f7f7;
     bottom: 0;
     box-shadow: 0 -22px 40px rgb(247, 247, 247);
+    left: 0;
     margin: 0;
     max-width: 736px;
-    padding-left: 0px;
-    padding-right: 48px;
+    padding-left: 24px;
+    padding-right: 24px;
     position: fixed;
     z-index: 2;
   }
@@ -370,14 +371,10 @@
   #main-content {
     padding-bottom: 40px;
   }
-
-  #details {
-    padding-top: 5.5vh;
-  }
 }
 
-@media (max-width: 420px) and (orientation: portrait),
-       (max-height: 736px) {
+@media (max-width: 420px) and (max-height: 736px) and (orientation: portrait),
+       (max-width: 736px) and (max-height: 420px) and (orientation: landscape) {
   body {
     margin: 0 auto;
   }
@@ -417,7 +414,6 @@
     height: 0;
     opacity: 0;
     overflow: hidden;
-    padding-bottom: 0;
     transition: none;
   }
 
@@ -432,12 +428,12 @@
   }
 
   .icon {
-    margin-bottom: 5.69vh;
+    margin-bottom: 12px;
   }
 
   .interstitial-wrapper {
     box-sizing: border-box;
-    margin: 7vh auto 12px;
+    margin: 24px auto 12px;
     padding: 0 24px;
     position: relative;
   }
@@ -464,34 +460,151 @@
   }
 }
 
-@media (min-width: 421px) and (min-height: 500px) and (max-height: 736px) {
-  .interstitial-wrapper {
-    margin-top: 10vh;
-  }
-}
-
 @media (min-height: 400px) and (orientation:portrait) {
   .interstitial-wrapper {
     margin-bottom: 145px;
   }
 }
 
-@media (min-height: 299px) {
+@media (min-height: 299px) and (orientation:portrait) {
   .nav-wrapper {
     padding-bottom: 16px;
   }
 }
 
-@media (min-height: 500px) and (max-height: 650px) and (max-width: 414px) and
-       (orientation: portrait) {
+@media (min-height: 405px) and (max-height: 736px) and
+       (max-width: 420px) and (orientation:portrait) {
+  .icon {
+    margin-bottom: 24px;
+  }
+
   .interstitial-wrapper {
-    margin-top: 7vh;
+    margin-top: 64px;
   }
 }
 
-@media (min-height: 650px) and (max-width: 414px) and (orientation: portrait) {
+@media (min-height: 480px) and (max-width: 420px) and
+       (max-height: 736px) and (orientation: portrait),
+       (min-height: 338px) and (max-height: 420px) and (max-width: 736px) and
+       (orientation: landscape) {
+  .icon {
+    margin-bottom: 24px;
+  }
+
+  .nav-wrapper {
+    padding-bottom: 24px;
+  }
+}
+
+@media (min-height: 500px) and (max-width: 414px) and (orientation: portrait) {
   .interstitial-wrapper {
-    margin-top: 10vh;
+    margin-top: 96px;
+  }
+}
+
+/* Phablet sizing */
+@media (min-width: 375px) and (min-height: 641px) and (max-height: 736px) and
+       (max-width: 414px) and (orientation: portrait) {
+  button,
+  [dir='rtl'] button,
+  .small-link {
+    font-size: 1em;
+    padding-bottom: 12px;
+    padding-top: 12px;
+  }
+
+  body:not(.offline) .icon {
+    height: 80px;
+    width: 80px;
+  }
+
+  #details-button {
+    margin-top: 28px;
+  }
+
+  h1 {
+    font-size: 1.7em;
+  }
+
+  .icon {
+    margin-bottom: 28px;
+  }
+
+  .interstitial-wrapper {
+    padding: 28px;
+  }
+
+  .interstitial-wrapper p {
+    font-size: 1.05em;
+  }
+
+  .nav-wrapper {
+    padding: 28px;
+  }
+}
+
+@media (min-width: 420px) and (max-width: 736px) and
+       (min-height: 240px) and (max-height: 298px) and
+       (orientation:landscape) {
+  body:not(.offline) .icon {
+    height: 50px;
+    width: 50px;
+  }
+
+  .icon {
+    padding-top: 0;
+  }
+
+  .interstitial-wrapper {
+    margin-top: 16px;
+  }
+
+  .nav-wrapper {
+    padding: 0 24px 8px;
+  }
+}
+
+@media (min-width: 420px) and (max-width: 736px) and
+       (min-height: 240px) and (max-height: 420px) and
+       (orientation:landscape) {
+  #details-button {
+    margin: 0;
+  }
+
+  .interstitial-wrapper {
+    margin-bottom: 70px;
+  }
+
+  .nav-wrapper {
+    margin-top: 0;
+  }
+
+  #extended-reporting-opt-in {
+    margin-top: 0;
+  }
+}
+
+/* Phablet landscape */
+@media (min-width: 680px) and (max-height: 414px) {
+  .interstitial-wrapper {
+    margin: 24px auto;
+  }
+
+  .nav-wrapper {
+    margin: 16px auto 0;
+  }
+}
+
+@media (max-height: 240px) and (orientation: landscape),
+       (max-height: 480px) and (orientation: portrait),
+       (max-width: 419px) and (max-height: 323px) {
+  body:not(.offline) .icon {
+    height: 56px;
+    width: 56px;
+  }
+
+  .icon {
+    margin-bottom: 16px;
   }
 }
 
diff --git a/components/security_interstitials/core/browser/resources/interstitial_v2_mobile.js b/components/security_interstitials/core/browser/resources/interstitial_v2_mobile.js
index 8980099..f2962323 100644
--- a/components/security_interstitials/core/browser/resources/interstitial_v2_mobile.js
+++ b/components/security_interstitials/core/browser/resources/interstitial_v2_mobile.js
@@ -12,9 +12,10 @@
   var helpOuterBox = document.querySelector('#details');
   var mainContent = document.querySelector('#main-content');
   var mediaQuery = '(min-width: 240px) and (max-width: 420px) and ' +
-      '(min-height: 401px), ' +
-      '(max-height: 736px) and (min-height: 240px) and ' +
-      '(min-width: 421px)';
+      '(max-height: 736px) and (min-height: 401px) and ' +
+      '(orientation: portrait), (max-width: 736px) and ' +
+      '(max-height: 420px) and (min-height: 240px) and ' +
+      '(min-width: 421px) and (orientation: landscape)';
 
   var detailsHidden = helpOuterBox.classList.contains('hidden');
   var runnerContainer = document.querySelector('.runner-container');
diff --git a/infra/config/cq.cfg b/infra/config/cq.cfg
index 72fcb3b..b9cb514 100644
--- a/infra/config/cq.cfg
+++ b/infra/config/cq.cfg
@@ -3,14 +3,12 @@
 
 version: 1
 cq_name: "chromium"
+in_production: false
 cq_status_url: "https://chromium-cq-status.appspot.com"
 git_repo_url: "https://chromium.googlesource.com/chromium/src"
 commit_burst_delay: 60
 max_commit_burst: 2
 
-# Drain the CQ (crbug.com/570421)
-draining_start_time: "2017-04-27T21:20:30Z"
-
 gerrit {}
 rietveld {
   url: "https://codereview.chromium.org"
diff --git a/ui/base/clipboard/clipboard_android.cc b/ui/base/clipboard/clipboard_android.cc
index b33b78f..1852f24 100644
--- a/ui/base/clipboard/clipboard_android.cc
+++ b/ui/base/clipboard/clipboard_android.cc
@@ -5,10 +5,12 @@
 #include "ui/base/clipboard/clipboard_android.h"
 
 #include <algorithm>
+#include <utility>
 
 #include "base/android/context_utils.h"
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
+#include "base/callback.h"
 #include "base/lazy_instance.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -52,6 +54,7 @@
 class ClipboardMap {
  public:
   ClipboardMap();
+  void SetModifiedCallback(ClipboardAndroid::ModifiedCallback cb);
   std::string Get(const std::string& format);
   uint64_t GetSequenceNumber() const;
   base::Time GetLastModifiedTime() const;
@@ -62,6 +65,9 @@
   void CommitToAndroidClipboard();
   void Clear();
 
+  // Unlike the functions above, does not call |modified_cb_|.
+  void SetLastModifiedTimeWithoutRunningCallback(base::Time time);
+
  private:
   enum class MapState {
     kOutOfDate,
@@ -69,7 +75,12 @@
     kPreparingCommit,
   };
 
+  // Updates |last_modified_time_| to |time| and writes it to |local_state_|.
+  void UpdateLastModifiedTime(base::Time time);
+
+  // Updates |map_| and |map_state_| if necessary by fetching data from Java.
   void UpdateFromAndroidClipboard();
+
   std::map<std::string, std::string> map_;
   MapState map_state_;
   base::Lock lock_;
@@ -77,6 +88,8 @@
   uint64_t sequence_number_;
   base::Time last_modified_time_;
 
+  ClipboardAndroid::ModifiedCallback modified_cb_;
+
   // Java class and methods for the Android ClipboardManager.
   ScopedJavaGlobalRef<jobject> clipboard_manager_;
 };
@@ -87,6 +100,10 @@
   DCHECK(clipboard_manager_.obj());
 }
 
+void ClipboardMap::SetModifiedCallback(ClipboardAndroid::ModifiedCallback cb) {
+  modified_cb_ = std::move(cb);
+}
+
 std::string ClipboardMap::Get(const std::string& format) {
   base::AutoLock lock(lock_);
   UpdateFromAndroidClipboard();
@@ -103,7 +120,7 @@
 }
 
 void ClipboardMap::ClearLastModifiedTime() {
-  last_modified_time_ = base::Time();
+  UpdateLastModifiedTime(base::Time());
 }
 
 bool ClipboardMap::HasFormat(const std::string& format) {
@@ -114,7 +131,7 @@
 
 void ClipboardMap::OnPrimaryClipboardChanged() {
   sequence_number_++;
-  last_modified_time_ = base::Time::Now();
+  UpdateLastModifiedTime(base::Time::Now());
   map_state_ = MapState::kOutOfDate;
 }
 
@@ -151,7 +168,7 @@
   }
   map_state_ = MapState::kUpToDate;
   sequence_number_++;
-  last_modified_time_ = base::Time::Now();
+  UpdateLastModifiedTime(base::Time::Now());
 }
 
 void ClipboardMap::Clear() {
@@ -161,7 +178,11 @@
   Java_Clipboard_clear(env, clipboard_manager_);
   map_state_ = MapState::kUpToDate;
   sequence_number_++;
-  last_modified_time_ = base::Time::Now();
+  UpdateLastModifiedTime(base::Time::Now());
+}
+
+void ClipboardMap::SetLastModifiedTimeWithoutRunningCallback(base::Time time) {
+  last_modified_time_ = time;
 }
 
 // Add a key:jstr pair to map, but only if jstr is not null, and also
@@ -177,6 +198,13 @@
   }
 }
 
+void ClipboardMap::UpdateLastModifiedTime(base::Time time) {
+  last_modified_time_ = time;
+  // |modified_callback_| may be null in tests.
+  if (modified_cb_)
+    modified_cb_.Run(time);
+}
+
 void ClipboardMap::UpdateFromAndroidClipboard() {
   DCHECK_NE(MapState::kPreparingCommit, map_state_);
   if (map_state_ == MapState::kUpToDate)
@@ -303,6 +331,15 @@
   g_map.Get().OnPrimaryClipboardChanged();
 }
 
+void ClipboardAndroid::SetModifiedCallback(ModifiedCallback cb) {
+  g_map.Get().SetModifiedCallback(std::move(cb));
+}
+
+void ClipboardAndroid::SetLastModifiedTimeWithoutRunningCallback(
+    base::Time time) {
+  g_map.Get().SetLastModifiedTimeWithoutRunningCallback(time);
+}
+
 ClipboardAndroid::ClipboardAndroid() {
   DCHECK(CalledOnValidThread());
 }
diff --git a/ui/base/clipboard/clipboard_android.h b/ui/base/clipboard/clipboard_android.h
index 8589e9c..716cc387 100644
--- a/ui/base/clipboard/clipboard_android.h
+++ b/ui/base/clipboard/clipboard_android.h
@@ -12,6 +12,7 @@
 #include <stdint.h>
 
 #include "base/android/scoped_java_ref.h"
+#include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/time/time.h"
 
@@ -19,11 +20,22 @@
 
 class ClipboardAndroid : public Clipboard {
  public:
+  // Callback called whenever the clipboard is modified.  The parameter
+  // represents the time of the modification.
+  using ModifiedCallback = base::Callback<void(base::Time)>;
+
   // Called by Java when the Java Clipboard is notified that the clipboard has
   // changed.
   void OnPrimaryClipChanged(JNIEnv* env,
                             const base::android::JavaParamRef<jobject>& obj);
 
+  // Sets the callback called whenever the clipboard is modified.
+  UI_BASE_EXPORT void SetModifiedCallback(ModifiedCallback cb);
+
+  // Sets the last modified time without calling the above callback.
+  UI_BASE_EXPORT void SetLastModifiedTimeWithoutRunningCallback(
+      base::Time time);
+
  private:
   friend class Clipboard;