Support Kerberos on Android

As part of this, allow asynchronous token return. This should be
allowed anyway, but is particularly important on Android, where getting
a token may cause user interaction.

BUG=474943

Review URL: https://codereview.chromium.org/1128043007

Cr-Commit-Position: refs/heads/master@{#338039}
diff --git a/build/android/pylib/gtest/test_package_apk.py b/build/android/pylib/gtest/test_package_apk.py
index 16ef21c6..a679b03 100644
--- a/build/android/pylib/gtest/test_package_apk.py
+++ b/build/android/pylib/gtest/test_package_apk.py
@@ -43,6 +43,11 @@
     else:
       self._package_info = constants.PACKAGE_INFO['gtest']
 
+    if suite_name == 'net_unittests':
+      self._extras = {'RunInSubThread': ''}
+    else:
+      self._extras = []
+
   def _CreateCommandLineFileOnDevice(self, device, options):
     device.WriteFile(self._package_info.cmdline_file,
                      self.suite_name + ' ' + options)
@@ -74,7 +79,8 @@
     device.StartActivity(
         intent.Intent(package=self._package_info.package,
                       activity=self._package_info.activity,
-                      action='android.intent.action.MAIN'),
+                      action='android.intent.action.MAIN',
+                      extras=self._extras),
         # No wait since the runner waits for FIFO creation anyway.
         blocking=False,
         force_stop=force_stop)
diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc
index 6d2307533e..43eb98bc 100644
--- a/chrome/browser/io_thread.cc
+++ b/chrome/browser/io_thread.cc
@@ -505,6 +505,8 @@
   auth_delegate_whitelist_ = local_state->GetString(
       prefs::kAuthNegotiateDelegateWhitelist);
   gssapi_library_name_ = local_state->GetString(prefs::kGSSAPILibraryName);
+  auth_android_negotiate_account_type_ =
+      local_state->GetString(prefs::kAuthAndroidNegotiateAccountType);
   pref_proxy_config_tracker_.reset(
       ProxyServiceFactory::CreatePrefProxyConfigTrackerOfLocalState(
           local_state));
@@ -1039,6 +1041,8 @@
   registry->RegisterStringPref(prefs::kAuthNegotiateDelegateWhitelist,
                                std::string());
   registry->RegisterStringPref(prefs::kGSSAPILibraryName, std::string());
+  registry->RegisterStringPref(prefs::kAuthAndroidNegotiateAccountType,
+                               std::string());
   registry->RegisterStringPref(
       data_reduction_proxy::prefs::kDataReductionProxy, std::string());
   registry->RegisterBooleanPref(prefs::kEnableReferrers, true);
@@ -1067,9 +1071,9 @@
 
   scoped_ptr<net::HttpAuthHandlerRegistryFactory> registry_factory(
       net::HttpAuthHandlerRegistryFactory::Create(
-          supported_schemes, globals_->url_security_manager.get(),
-          resolver, gssapi_library_name_, negotiate_disable_cname_lookup_,
-          negotiate_enable_port_));
+          supported_schemes, globals_->url_security_manager.get(), resolver,
+          gssapi_library_name_, auth_android_negotiate_account_type_,
+          negotiate_disable_cname_lookup_, negotiate_enable_port_));
   return registry_factory.release();
 }
 
diff --git a/chrome/browser/io_thread.h b/chrome/browser/io_thread.h
index 1066a9b..54a90ff0 100644
--- a/chrome/browser/io_thread.h
+++ b/chrome/browser/io_thread.h
@@ -457,6 +457,7 @@
   std::string auth_server_whitelist_;
   std::string auth_delegate_whitelist_;
   std::string gssapi_library_name_;
+  std::string auth_android_negotiate_account_type_;
 
   // This is an instance of the default SSLConfigServiceManager for the current
   // platform and it gets SSL preferences from local_state object.
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 46ec969..716be31 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -1686,6 +1686,11 @@
 // String that specifies the name of a custom GSSAPI library to load.
 const char kGSSAPILibraryName[] = "auth.gssapi_library_name";
 
+// String that specifies the Android account type to use for Negotiate
+// authentication.
+const char kAuthAndroidNegotiateAccountType[] =
+    "auth.android_negotiate_account_type";
+
 // Boolean that specifies whether to allow basic auth prompting on cross-
 // domain sub-content requests.
 const char kAllowCrossOriginAuthPrompt[] = "auth.allow_cross_origin_prompt";
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 4b4fa6b..6223df1 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -693,6 +693,7 @@
 extern const char kAuthServerWhitelist[];
 extern const char kAuthNegotiateDelegateWhitelist[];
 extern const char kGSSAPILibraryName[];
+extern const char kAuthAndroidNegotiateAccountType[];
 extern const char kAllowCrossOriginAuthPrompt[];
 
 extern const char kBuiltInDnsClientEnabled[];
diff --git a/google_apis/gcm/tools/mcs_probe.cc b/google_apis/gcm/tools/mcs_probe.cc
index eadd3b5..9d0881b0 100644
--- a/google_apis/gcm/tools/mcs_probe.cc
+++ b/google_apis/gcm/tools/mcs_probe.cc
@@ -377,14 +377,9 @@
 
   transport_security_state_.reset(new net::TransportSecurityState());
   url_security_manager_.reset(net::URLSecurityManager::Create(NULL, NULL));
-  http_auth_handler_factory_.reset(
-      net::HttpAuthHandlerRegistryFactory::Create(
-          std::vector<std::string>(1, "basic"),
-          url_security_manager_.get(),
-          host_resolver_.get(),
-          std::string(),
-          false,
-          false));
+  http_auth_handler_factory_.reset(net::HttpAuthHandlerRegistryFactory::Create(
+      std::vector<std::string>(1, "basic"), url_security_manager_.get(),
+      host_resolver_.get(), std::string(), std::string(), false, false));
   http_server_properties_.reset(new net::HttpServerPropertiesImpl());
   host_mapping_rules_.reset(new net::HostMappingRules());
   proxy_service_.reset(net::ProxyService::CreateDirectWithNetLog(&net_log_));
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 8454d7a..e9ad1d2 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -1216,6 +1216,7 @@
       "android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
       "android/java/src/org/chromium/net/AndroidPrivateKey.java",
       "android/java/src/org/chromium/net/GURLUtils.java",
+      "android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
       "android/java/src/org/chromium/net/NetworkChangeNotifier.java",
       "android/java/src/org/chromium/net/ProxyChangeListener.java",
       "android/java/src/org/chromium/net/X509Util.java",
@@ -1225,6 +1226,7 @@
   generate_jni("net_test_jni_headers") {
     sources = [
       "android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java",
+      "test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java",
     ]
     jni_package = "net"
   }
@@ -1408,14 +1410,20 @@
 
     if (use_kerberos) {
       defines += [ "USE_KERBEROS" ]
-    } else {
+    }
+
+    # These are excluded on Android, because the actual Kerberos support, which
+    # these test, is in a separate app on Android.
+    if (!use_kerberos || is_android) {
       sources -= [
         "http/http_auth_gssapi_posix_unittest.cc",
-        "http/http_auth_handler_negotiate_unittest.cc",
         "http/mock_gssapi_library_posix.cc",
         "http/mock_gssapi_library_posix.h",
       ]
     }
+    if (!use_kerberos) {
+      sources -= [ "http/http_auth_handler_negotiate_unittest.cc" ]
+    }
 
     if (use_openssl || (!is_desktop_linux && !is_chromeos && !is_ios)) {
       # Only include this test when on Posix and using NSS for
diff --git a/net/android/BUILD.gn b/net/android/BUILD.gn
index 358e1f7..abe4654 100644
--- a/net/android/BUILD.gn
+++ b/net/android/BUILD.gn
@@ -30,6 +30,7 @@
   DEPRECATED_java_in_dir = "../test/android/javatests/src"
   deps = [
     "//base:base_java",
+    ":net_java",
   ]
   srcjar_deps = [ ":net_java_test_support_enums_srcjar" ]
 }
@@ -78,6 +79,17 @@
   ]
 }
 
+junit_binary("net_junit_tests") {
+  java_files =
+      [ "junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java" ]
+  deps = [
+    ":net_java",
+    "//base:base_java",
+    "//base:base_java_test_support",
+    "//third_party/junit:hamcrest",
+  ]
+}
+
 # TODO(GYP)
 if (false) {
   unittest_apk("net_unittests_apk") {
diff --git a/net/android/dummy_spnego_authenticator.cc b/net/android/dummy_spnego_authenticator.cc
new file mode 100644
index 0000000..d42f64c
--- /dev/null
+++ b/net/android/dummy_spnego_authenticator.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 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 "net/android/dummy_spnego_authenticator.h"
+
+#include "base/android/jni_string.h"
+#include "base/base64.h"
+#include "net/test/jni/DummySpnegoAuthenticator_jni.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+// iso.org.dod.internet.security.mechanism.snego (1.3.6.1.5.5.2)
+// From RFC 4178, which uses SNEGO not SPNEGO.
+static const unsigned char kSpnegoOid[] = {0x2b, 0x06, 0x01, 0x05, 0x05, 0x02};
+gss_OID_desc CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL = {
+    arraysize(kSpnegoOid),
+    const_cast<unsigned char*>(kSpnegoOid)};
+
+gss_OID CHROME_GSS_SPNEGO_MECH_OID_DESC = &CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL;
+
+namespace {
+
+// gss_OID helpers.
+// NOTE: gss_OID's do not own the data they point to, which should be static.
+void ClearOid(gss_OID dest) {
+  if (!dest)
+    return;
+  dest->length = 0;
+  dest->elements = NULL;
+}
+
+void SetOid(gss_OID dest, const void* src, size_t length) {
+  if (!dest)
+    return;
+  ClearOid(dest);
+  if (!src)
+    return;
+  dest->length = length;
+  if (length)
+    dest->elements = const_cast<void*>(src);
+}
+
+void CopyOid(gss_OID dest, const gss_OID_desc* src) {
+  if (!dest)
+    return;
+  ClearOid(dest);
+  if (!src)
+    return;
+  SetOid(dest, src->elements, src->length);
+}
+
+}  // namespace
+
+namespace test {
+
+GssContextMockImpl::GssContextMockImpl()
+    : lifetime_rec(0), ctx_flags(0), locally_initiated(0), open(0) {
+  ClearOid(&mech_type);
+}
+
+GssContextMockImpl::GssContextMockImpl(const GssContextMockImpl& other)
+    : src_name(other.src_name),
+      targ_name(other.targ_name),
+      lifetime_rec(other.lifetime_rec),
+      ctx_flags(other.ctx_flags),
+      locally_initiated(other.locally_initiated),
+      open(other.open) {
+  CopyOid(&mech_type, &other.mech_type);
+}
+
+GssContextMockImpl::GssContextMockImpl(const char* src_name_in,
+                                       const char* targ_name_in,
+                                       uint32_t lifetime_rec_in,
+                                       const gss_OID_desc& mech_type_in,
+                                       uint32_t ctx_flags_in,
+                                       int locally_initiated_in,
+                                       int open_in)
+    : src_name(src_name_in ? src_name_in : ""),
+      targ_name(targ_name_in ? targ_name_in : ""),
+      lifetime_rec(lifetime_rec_in),
+      ctx_flags(ctx_flags_in),
+      locally_initiated(locally_initiated_in),
+      open(open_in) {
+  CopyOid(&mech_type, &mech_type_in);
+}
+
+GssContextMockImpl::~GssContextMockImpl() {
+  ClearOid(&mech_type);
+}
+
+}  // namespace test
+
+namespace android {
+
+DummySpnegoAuthenticator::SecurityContextQuery::SecurityContextQuery(
+    const std::string& in_expected_package,
+    uint32_t in_response_code,
+    uint32_t in_minor_response_code,
+    const test::GssContextMockImpl& in_context_info,
+    const std::string& in_expected_input_token,
+    const std::string& in_output_token)
+    : expected_package(in_expected_package),
+      response_code(in_response_code),
+      minor_response_code(in_minor_response_code),
+      context_info(in_context_info),
+      expected_input_token(in_expected_input_token),
+      output_token(in_output_token) {
+}
+
+DummySpnegoAuthenticator::SecurityContextQuery::SecurityContextQuery(
+    const std::string& in_expected_package,
+    uint32_t in_response_code,
+    uint32_t in_minor_response_code,
+    const test::GssContextMockImpl& in_context_info,
+    const char* in_expected_input_token,
+    const char* in_output_token)
+    : expected_package(in_expected_package),
+      response_code(in_response_code),
+      minor_response_code(in_minor_response_code),
+      context_info(in_context_info) {
+  if (in_expected_input_token)
+    expected_input_token = in_expected_input_token;
+  if (in_output_token)
+    output_token = in_output_token;
+}
+
+DummySpnegoAuthenticator::SecurityContextQuery::SecurityContextQuery()
+    : response_code(0), minor_response_code(0) {
+}
+
+DummySpnegoAuthenticator::SecurityContextQuery::~SecurityContextQuery() {
+}
+
+base::android::ScopedJavaLocalRef<jstring>
+DummySpnegoAuthenticator::SecurityContextQuery::GetTokenToReturn(
+    JNIEnv* env,
+    jobject /*obj*/) {
+  return base::android::ConvertUTF8ToJavaString(env, output_token.c_str());
+}
+int DummySpnegoAuthenticator::SecurityContextQuery::GetResult(JNIEnv* /*env*/,
+                                                              jobject /*obj*/) {
+  return response_code;
+}
+
+void DummySpnegoAuthenticator::SecurityContextQuery::CheckGetTokenArguments(
+    JNIEnv* env,
+    jobject /*obj*/,
+    jstring j_incoming_token) {
+  std::string incoming_token =
+      base::android::ConvertJavaStringToUTF8(env, j_incoming_token);
+  EXPECT_EQ(expected_input_token, incoming_token);
+}
+
+// Needed to satisfy "complex class" clang requirements.
+DummySpnegoAuthenticator::DummySpnegoAuthenticator() {
+}
+
+DummySpnegoAuthenticator::~DummySpnegoAuthenticator() {
+}
+
+void DummySpnegoAuthenticator::EnsureTestAccountExists() {
+  Java_DummySpnegoAuthenticator_ensureTestAccountExists(
+      base::android::AttachCurrentThread());
+}
+
+void DummySpnegoAuthenticator::RemoveTestAccounts() {
+  Java_DummySpnegoAuthenticator_removeTestAccounts(
+      base::android::AttachCurrentThread());
+}
+
+void DummySpnegoAuthenticator::ExpectSecurityContext(
+    const std::string& expected_package,
+    uint32_t response_code,
+    uint32_t minor_response_code,
+    const test::GssContextMockImpl& context_info,
+    const std::string& expected_input_token,
+    const std::string& output_token) {
+  SecurityContextQuery query(expected_package, response_code,
+                             minor_response_code, context_info,
+                             expected_input_token, output_token);
+  expected_security_queries_.push_back(query);
+  Java_DummySpnegoAuthenticator_setNativeAuthenticator(
+      base::android::AttachCurrentThread(), reinterpret_cast<intptr_t>(this));
+}
+
+bool DummySpnegoAuthenticator::RegisterJni(JNIEnv* env) {
+  return RegisterNativesImpl(env);
+}
+
+long DummySpnegoAuthenticator::GetNextQuery(JNIEnv* /*env*/,
+                                            jobject /* obj */) {
+  CheckQueueNotEmpty();
+  current_query_ = expected_security_queries_.front();
+  expected_security_queries_.pop_front();
+  return reinterpret_cast<intptr_t>(&current_query_);
+}
+
+void DummySpnegoAuthenticator::CheckQueueNotEmpty() {
+  ASSERT_FALSE(expected_security_queries_.empty());
+}
+
+}  // namespace android
+}  // namespace net
diff --git a/net/android/dummy_spnego_authenticator.h b/net/android/dummy_spnego_authenticator.h
new file mode 100644
index 0000000..a7d4a97
--- /dev/null
+++ b/net/android/dummy_spnego_authenticator.h
@@ -0,0 +1,140 @@
+// Copyright (c) 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.
+
+#ifndef NET_ANDROID_DUMMY_SPNEGO_AUTHENTICATOR_H_
+#define NET_ANDROID_DUMMY_SPNEGO_AUTHENTICATOR_H_
+
+#include <jni.h>
+#include <cstdint>
+#include <list>
+#include <string>
+
+#include "base/android/scoped_java_ref.h"
+
+// Provides an interface for controlling the DummySpnegoAuthenticator service.
+// This includes a basic stub of the Mock GSSAPI library, so that OS independent
+// Negotiate authentication tests can be run on Android.
+namespace net {
+
+// These constant values are arbitrary, and different from the real GSSAPI
+// values, but must match those used in DummySpnegoAuthenticator.java
+#define GSS_S_COMPLETE 0
+#define GSS_S_CONTINUE_NEEDED 1
+#define GSS_S_FAILURE 2
+
+class gss_buffer_desc;
+
+typedef struct gss_OID_desc_struct {
+  uint32_t length;
+  void* elements;
+} gss_OID_desc, *gss_OID;
+
+extern gss_OID CHROME_GSS_SPNEGO_MECH_OID_DESC;
+
+namespace test {
+
+// Copy of class in Mock GSSAPI library.
+class GssContextMockImpl {
+ public:
+  GssContextMockImpl();
+  GssContextMockImpl(const GssContextMockImpl& other);
+  GssContextMockImpl(const char* src_name,
+                     const char* targ_name,
+                     uint32_t lifetime_rec,
+                     const gss_OID_desc& mech_type,
+                     uint32_t ctx_flags,
+                     int locally_initiated,
+                     int open);
+  ~GssContextMockImpl();
+
+  void Assign(const GssContextMockImpl& other);
+
+  std::string src_name;
+  std::string targ_name;
+  int32_t lifetime_rec;
+  gss_OID_desc mech_type;
+  int32_t ctx_flags;
+  int locally_initiated;
+  int open;
+};
+
+}  // namespace test
+
+namespace android {
+
+// Interface to Java DummySpnegoAuthenticator.
+class DummySpnegoAuthenticator {
+ public:
+  struct SecurityContextQuery {
+    SecurityContextQuery(const std::string& expected_package,
+                         uint32_t response_code,
+                         uint32_t minor_response_code,
+                         const test::GssContextMockImpl& context_info,
+                         const std::string& expected_input_token,
+                         const std::string& output_token);
+    SecurityContextQuery(const std::string& expected_package,
+                         uint32_t response_code,
+                         uint32_t minor_response_code,
+                         const test::GssContextMockImpl& context_info,
+                         const char* expected_input_token,
+                         const char* output_token);
+    SecurityContextQuery();
+    ~SecurityContextQuery();
+
+    // Note that many of these fields only exist for compatibility with the
+    // non-Android version of the tests. Only the response_code and tokens are
+    // used or checked on Android.
+    std::string expected_package;
+    uint32_t response_code;
+    uint32_t minor_response_code;
+    test::GssContextMockImpl context_info;
+    std::string expected_input_token;
+    std::string output_token;
+
+    // Java callable members
+    base::android::ScopedJavaLocalRef<jstring> GetTokenToReturn(JNIEnv* env,
+                                                                jobject obj);
+    int GetResult(JNIEnv* env, jobject obj);
+
+    // Called from Java to check the arguments passed to the GetToken. Has to
+    // be in C++ since these tests are driven by googletest, and can only report
+    // failures through the googletest C++ API.
+    void CheckGetTokenArguments(JNIEnv* env,
+                                jobject obj,
+                                jstring incoming_token);
+  };
+
+  DummySpnegoAuthenticator();
+
+  ~DummySpnegoAuthenticator();
+
+  void ExpectSecurityContext(const std::string& expected_package,
+                             uint32_t response_code,
+                             uint32_t minor_response_code,
+                             const test::GssContextMockImpl& context_info,
+                             const std::string& expected_input_token,
+                             const std::string& output_token);
+
+  static bool RegisterJni(JNIEnv* env);
+
+  static void EnsureTestAccountExists();
+  static void RemoveTestAccounts();
+
+  long GetNextQuery(JNIEnv* env, jobject obj);
+
+ private:
+  // Abandon the test if the query queue is empty. Has to be a void function to
+  // allow use of ASSERT_FALSE.
+  void CheckQueueNotEmpty();
+
+  std::list<SecurityContextQuery> expected_security_queries_;
+  // Needed to keep the current query alive once it has been pulled from the
+  // queue. This is simpler than transferring its ownership to Java.
+  SecurityContextQuery current_query_;
+};
+
+}  // namespace android
+}  // namespace net
+
+#endif  // NET_ANDROID_DUMMY_SPNEGO_AUTHENTICATOR_DRIVER_H
diff --git a/net/android/http_auth_negotiate_android.cc b/net/android/http_auth_negotiate_android.cc
new file mode 100644
index 0000000..a8e9cc3
--- /dev/null
+++ b/net/android/http_auth_negotiate_android.cc
@@ -0,0 +1,154 @@
+// Copyright 2015 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 "net/android/http_auth_negotiate_android.h"
+
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/thread_task_runner_handle.h"
+#include "jni/HttpNegotiateAuthenticator_jni.h"
+#include "net/base/auth.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth_challenge_tokenizer.h"
+#include "net/http/http_auth_multi_round_parse.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ScopedJavaLocalRef;
+
+namespace net {
+namespace android {
+
+JavaNegotiateResultWrapper::JavaNegotiateResultWrapper(
+    const scoped_refptr<base::TaskRunner>& callback_task_runner,
+    const base::Callback<void(int, const std::string&)>& thread_safe_callback)
+    : callback_task_runner_(callback_task_runner),
+      thread_safe_callback_(thread_safe_callback) {
+}
+
+JavaNegotiateResultWrapper::~JavaNegotiateResultWrapper() {
+}
+
+void JavaNegotiateResultWrapper::SetResult(JNIEnv* env,
+                                           jobject obj,
+                                           int result,
+                                           jstring token) {
+  // This will be called on the UI thread, so we have to post a task back to the
+  // correct thread to actually save the result
+  std::string raw_token = ConvertJavaStringToUTF8(env, token);
+  // Always post, even if we are on the same thread. This guarantees that the
+  // result will be delayed until after the request has completed, which
+  // simplifies the logic. In practice the result will only ever come back on
+  // the original thread in an obscure error case.
+  callback_task_runner_->PostTask(
+      FROM_HERE, base::Bind(thread_safe_callback_, result, raw_token));
+  // We will always get precisely one call to set result for each call to
+  // getNextAuthToken, so we can now delete the callback object, and must
+  // do so to avoid a memory leak.
+  delete this;
+}
+
+HttpAuthNegotiateAndroid::HttpAuthNegotiateAndroid(
+    const std::string& account_type)
+    : account_type_(account_type),
+      can_delegate_(false),
+      first_challenge_(true),
+      auth_token_(nullptr),
+      weak_factory_(this) {
+  DCHECK(!account_type.empty());
+  JNIEnv* env = AttachCurrentThread();
+  java_authenticator_.Reset(Java_HttpNegotiateAuthenticator_create(
+      env, ConvertUTF8ToJavaString(env, account_type).obj()));
+}
+
+HttpAuthNegotiateAndroid::~HttpAuthNegotiateAndroid() {
+}
+
+bool HttpAuthNegotiateAndroid::Register(JNIEnv* env) {
+  return RegisterNativesImpl(env);
+}
+
+bool HttpAuthNegotiateAndroid::Init() {
+  return true;
+}
+
+bool HttpAuthNegotiateAndroid::NeedsIdentity() const {
+  return false;
+}
+
+bool HttpAuthNegotiateAndroid::AllowsExplicitCredentials() const {
+  return false;
+}
+
+HttpAuth::AuthorizationResult HttpAuthNegotiateAndroid::ParseChallenge(
+    net::HttpAuthChallengeTokenizer* tok) {
+  if (first_challenge_) {
+    first_challenge_ = false;
+    return net::ParseFirstRoundChallenge("negotiate", tok);
+  }
+  std::string decoded_auth_token;
+  return net::ParseLaterRoundChallenge("negotiate", tok, &server_auth_token_,
+                                       &decoded_auth_token);
+}
+
+int HttpAuthNegotiateAndroid::GenerateAuthToken(
+    const AuthCredentials* credentials,
+    const std::string& spn,
+    std::string* auth_token,
+    const net::CompletionCallback& callback) {
+  DCHECK(auth_token);
+  DCHECK(completion_callback_.is_null());
+  DCHECK(!callback.is_null());
+
+  auth_token_ = auth_token;
+  completion_callback_ = callback;
+  scoped_refptr<base::SingleThreadTaskRunner> callback_task_runner =
+      base::ThreadTaskRunnerHandle::Get();
+  base::Callback<void(int, const std::string&)> thread_safe_callback =
+      base::Bind(&HttpAuthNegotiateAndroid::SetResultInternal,
+                 weak_factory_.GetWeakPtr());
+  JNIEnv* env = AttachCurrentThread();
+  ScopedJavaLocalRef<jstring> java_server_auth_token =
+      ConvertUTF8ToJavaString(env, server_auth_token_);
+  ScopedJavaLocalRef<jstring> java_spn = ConvertUTF8ToJavaString(env, spn);
+  ScopedJavaLocalRef<jstring> java_account_type =
+      ConvertUTF8ToJavaString(env, account_type_);
+
+  // It is intentional that callback_wrapper is not owned or deleted by the
+  // HttpAuthNegotiateAndroid object. The Java code will call the callback
+  // asynchronously on a different thread, and needs an object to call it on. As
+  // such, the callback_wrapper must not be deleted until the callback has been
+  // called, whatever happens to the HttpAuthNegotiateAndroid object.
+  //
+  // Unfortunately we have no automated way of managing C++ objects owned by
+  // Java, so the Java code must simply be written to guarantee that the
+  // callback is, in the end, called.
+  JavaNegotiateResultWrapper* callback_wrapper = new JavaNegotiateResultWrapper(
+      callback_task_runner, thread_safe_callback);
+  Java_HttpNegotiateAuthenticator_getNextAuthToken(
+      env, java_authenticator_.obj(),
+      reinterpret_cast<intptr_t>(callback_wrapper), java_spn.obj(),
+      java_server_auth_token.obj(), can_delegate_);
+  return ERR_IO_PENDING;
+}
+
+void HttpAuthNegotiateAndroid::Delegate() {
+  can_delegate_ = true;
+}
+
+void HttpAuthNegotiateAndroid::SetResultInternal(int result,
+                                                 const std::string& raw_token) {
+  DCHECK(auth_token_);
+  DCHECK(!completion_callback_.is_null());
+  if (result == OK)
+    *auth_token_ = "Negotiate " + raw_token;
+  base::ResetAndReturn(&completion_callback_).Run(result);
+}
+
+}  // namespace android
+}  // namespace net
diff --git a/net/android/http_auth_negotiate_android.h b/net/android/http_auth_negotiate_android.h
new file mode 100644
index 0000000..56990ce
--- /dev/null
+++ b/net/android/http_auth_negotiate_android.h
@@ -0,0 +1,132 @@
+// Copyright 2015 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 NET_ANDROID_HTTP_AUTH_NEGOTIATE_ANDROID_H_
+#define NET_ANDROID_HTTP_AUTH_NEGOTIATE_ANDROID_H_
+
+#include <jni.h>
+#include <string>
+
+#include "base/android/jni_android.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/http/http_auth.h"
+
+namespace net {
+
+class HttpAuthChallengeTokenizer;
+
+namespace android {
+
+// This class provides a threadsafe wrapper for SetResult, which is called from
+// Java. A new instance of this class is needed for each call, and the instance
+// destroys itself when the callback is received. It is written to allow
+// setResult to be called on any thread, but in practice they will be called
+// on the application's main thread.
+//
+// We cannot use a Callback object here, because there is no way of invoking the
+// Run method from Java.
+class NET_EXPORT_PRIVATE JavaNegotiateResultWrapper {
+ public:
+  scoped_refptr<base::TaskRunner> callback_task_runner_;
+  base::Callback<void(int, const std::string&)> thread_safe_callback_;
+
+  JavaNegotiateResultWrapper(
+      const scoped_refptr<base::TaskRunner>& callback_task_runner,
+      const base::Callback<void(int, const std::string&)>&
+          thread_safe_callback);
+
+  void SetResult(JNIEnv* env, jobject obj, int result, jstring token);
+
+ private:
+  // Class is only allowed to delete itself, nobody else is allowed to delete.
+  ~JavaNegotiateResultWrapper();
+};
+
+// Class providing Negotiate (SPNEGO/Kerberos) authentication support on
+// Android. The actual authentication is done through an Android authenticator
+// provided by third parties who want Kerberos support. This class simply
+// provides a bridge to the Java code, and hence to the service. See
+// https://drive.google.com/open?id=1G7WAaYEKMzj16PTHT_cIYuKXJG6bBcrQ7QQBQ6ihOcQ&authuser=1
+// for the full details.
+class NET_EXPORT_PRIVATE HttpAuthNegotiateAndroid {
+ public:
+  // Creates an object for one negotiation session. |account_type| is the
+  // Android account type, used by Android to find the correct authenticator.
+  explicit HttpAuthNegotiateAndroid(const std::string& account_type);
+  ~HttpAuthNegotiateAndroid();
+
+  // Register the JNI for this class.
+  static bool Register(JNIEnv* env);
+
+  // Does nothing, but needed for compatibility with the Negotiate
+  // authenticators for other O.S.. Always returns true.
+  bool Init();
+
+  // True if authentication needs the identity of the user from Chrome.
+  bool NeedsIdentity() const;
+
+  // True authentication can use explicit credentials included in the URL.
+  bool AllowsExplicitCredentials() const;
+
+  // Parse a received Negotiate challenge.
+  HttpAuth::AuthorizationResult ParseChallenge(
+      net::HttpAuthChallengeTokenizer* tok);
+
+  // Generates an authentication token.
+  //
+  // The return value is an error code. The authentication token will be
+  // returned in |*auth_token|. If the result code is not |OK|, the value of
+  // |*auth_token| is unspecified.
+  //
+  // If the operation cannot be completed synchronously, |ERR_IO_PENDING| will
+  // be returned and the real result code will be passed to the completion
+  // callback.  Otherwise the result code is returned immediately from this
+  // call.
+  //
+  // If the AndroidAuthNegotiate object is deleted before completion then the
+  // callback will not be called.
+  //
+  // If no immediate result is returned then |auth_token| must remain valid
+  // until the callback has been called.
+  //
+  // |spn| is the Service Principal Name of the server that the token is
+  // being generated for.
+  //
+  // If this is the first round of a multiple round scheme, credentials are
+  // obtained using |*credentials|. If |credentials| is NULL, the default
+  // credentials are used instead.
+  int GenerateAuthToken(const AuthCredentials* credentials,
+                        const std::string& spn,
+                        std::string* auth_token,
+                        const net::CompletionCallback& callback);
+
+  // Delegation is allowed on the Kerberos ticket. This allows certain servers
+  // to act as the user, such as an IIS server retrieving data from a
+  // Kerberized MSSQL server.
+  void Delegate();
+
+ private:
+  void SetResultInternal(int result, const std::string& token);
+
+  std::string account_type_;
+  bool can_delegate_;
+  bool first_challenge_;
+  std::string server_auth_token_;
+  std::string* auth_token_;
+  base::android::ScopedJavaGlobalRef<jobject> java_authenticator_;
+  net::CompletionCallback completion_callback_;
+
+  base::WeakPtrFactory<HttpAuthNegotiateAndroid> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(HttpAuthNegotiateAndroid);
+};
+
+}  // namespace android
+}  // namespace net
+
+#endif  // NET_ANDROID_HTTP_AUTH_NEGOTIATE_ANDROID_H_
diff --git a/net/android/http_auth_negotiate_android_unittest.cc b/net/android/http_auth_negotiate_android_unittest.cc
new file mode 100644
index 0000000..b1e178f
--- /dev/null
+++ b/net/android/http_auth_negotiate_android_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 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 "base/run_loop.h"
+#include "net/android/dummy_spnego_authenticator.h"
+#include "net/android/http_auth_negotiate_android.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_auth_challenge_tokenizer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace android {
+
+TEST(HttpAuthNegotiateAndroidTest, GenerateAuthToken) {
+  DummySpnegoAuthenticator::EnsureTestAccountExists();
+
+  std::string auth_token;
+
+  DummySpnegoAuthenticator authenticator;
+  net::test::GssContextMockImpl mockContext;
+  authenticator.ExpectSecurityContext("Negotiate", GSS_S_COMPLETE, 0,
+                                      mockContext, "", "DummyToken");
+
+  HttpAuthNegotiateAndroid auth("org.chromium.test.DummySpnegoAuthenticator");
+  EXPECT_TRUE(auth.Init());
+
+  TestCompletionCallback callback;
+  EXPECT_EQ(OK, callback.GetResult(auth.GenerateAuthToken(
+                    nullptr, "Dummy", &auth_token, callback.callback())));
+
+  EXPECT_EQ("Negotiate DummyToken", auth_token);
+
+  DummySpnegoAuthenticator::RemoveTestAccounts();
+}
+
+TEST(HttpAuthNegotiateAndroidTest, ParseChallenge_FirstRound) {
+  // The first round should just consist of an unadorned "Negotiate" header.
+  HttpAuthNegotiateAndroid auth("org.chromium.test.DummySpnegoAuthenticator");
+  std::string challenge_text = "Negotiate";
+  HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+                                       challenge_text.end());
+  EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+            auth.ParseChallenge(&challenge));
+}
+
+TEST(HttpAuthNegotiateAndroidTest, ParseChallenge_UnexpectedTokenFirstRound) {
+  // If the first round challenge has an additional authentication token, it
+  // should be treated as an invalid challenge from the server.
+  HttpAuthNegotiateAndroid auth("org.chromium.test.DummySpnegoAuthenticator");
+  std::string challenge_text = "Negotiate Zm9vYmFy";
+  HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+                                       challenge_text.end());
+  EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+            auth.ParseChallenge(&challenge));
+}
+
+TEST(HttpAuthNegotiateAndroidTest, ParseChallenge_TwoRounds) {
+  // The first round should just have "Negotiate", and the second round should
+  // have a valid base64 token associated with it.
+  HttpAuthNegotiateAndroid auth("org.chromium.test.DummySpnegoAuthenticator");
+  std::string first_challenge_text = "Negotiate";
+  HttpAuthChallengeTokenizer first_challenge(first_challenge_text.begin(),
+                                             first_challenge_text.end());
+  EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+            auth.ParseChallenge(&first_challenge));
+
+  std::string second_challenge_text = "Negotiate Zm9vYmFy";
+  HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
+                                              second_challenge_text.end());
+  EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+            auth.ParseChallenge(&second_challenge));
+}
+
+TEST(HttpAuthNegotiateAndroidTest, ParseChallenge_MissingTokenSecondRound) {
+  // If a later-round challenge is simply "Negotiate", it should be treated as
+  // an authentication challenge rejection from the server or proxy.
+  HttpAuthNegotiateAndroid auth("org.chromium.test.DummySpnegoAuthenticator");
+  std::string first_challenge_text = "Negotiate";
+  HttpAuthChallengeTokenizer first_challenge(first_challenge_text.begin(),
+                                             first_challenge_text.end());
+  EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+            auth.ParseChallenge(&first_challenge));
+
+  std::string second_challenge_text = "Negotiate";
+  HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
+                                              second_challenge_text.end());
+  EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+            auth.ParseChallenge(&second_challenge));
+}
+
+}  // namespace android
+}  // namespace net
diff --git a/net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java b/net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java
new file mode 100644
index 0000000..38dba5a
--- /dev/null
+++ b/net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java
@@ -0,0 +1,139 @@
+// Copyright 2015 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.net;
+
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
+
+import java.io.IOException;
+
+/**
+ * Class to get Auth Tokens for HTTP Negotiate authentication (typically used for Kerberos) An
+ * instance of this class is created for each separate negotiation.
+ */
+@JNINamespace("net::android")
+public class HttpNegotiateAuthenticator {
+    private Bundle mSpnegoContext = null;
+    private final String mAccountType;
+    private AccountManagerFuture<Bundle> mFuture;
+
+    private HttpNegotiateAuthenticator(String accountType) {
+        assert !android.text.TextUtils.isEmpty(accountType);
+        mAccountType = accountType;
+    }
+
+    /**
+     * @param accountType The Android account type to use.
+     */
+    @VisibleForTesting
+    @CalledByNative
+    static HttpNegotiateAuthenticator create(String accountType) {
+        return new HttpNegotiateAuthenticator(accountType);
+    }
+
+    /**
+     * @param nativeResultObject The C++ object used to return the result. For correct C++ memory
+     *            management we must call nativeSetResult precisely once with this object.
+     * @param principal The principal (must be host based).
+     * @param authToken The incoming auth token.
+     * @param canDelegate True if we can delegate.
+     */
+    @VisibleForTesting
+    @CalledByNative
+    void getNextAuthToken(final long nativeResultObject, final String principal, String authToken,
+            boolean canDelegate) {
+        assert principal != null;
+        String authTokenType = HttpNegotiateConstants.SPNEGO_TOKEN_TYPE_BASE + principal;
+        Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
+        if (activity == null) {
+            nativeSetResult(nativeResultObject, NetError.ERR_UNEXPECTED, null);
+            return;
+        }
+        AccountManager am = AccountManager.get(activity);
+        String features[] = {HttpNegotiateConstants.SPNEGO_FEATURE};
+
+        Bundle options = new Bundle();
+
+        if (authToken != null) {
+            options.putString(HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN, authToken);
+        }
+        if (mSpnegoContext != null) {
+            options.putBundle(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT, mSpnegoContext);
+        }
+        options.putBoolean(HttpNegotiateConstants.KEY_CAN_DELEGATE, canDelegate);
+
+        mFuture = am.getAuthTokenByFeatures(mAccountType, authTokenType, features, activity, null,
+                options, new AccountManagerCallback<Bundle>() {
+
+                    @Override
+                    public void run(AccountManagerFuture<Bundle> future) {
+                        try {
+                            Bundle result = future.getResult();
+                            mSpnegoContext =
+                                    result.getBundle(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT);
+                            int status;
+                            switch (result.getInt(HttpNegotiateConstants.KEY_SPNEGO_RESULT,
+                                    HttpNegotiateConstants.ERR_UNEXPECTED)) {
+                                case HttpNegotiateConstants.OK:
+                                    status = 0;
+                                    break;
+                                case HttpNegotiateConstants.ERR_UNEXPECTED:
+                                    status = NetError.ERR_UNEXPECTED;
+                                    break;
+                                case HttpNegotiateConstants.ERR_ABORTED:
+                                    status = NetError.ERR_ABORTED;
+                                    break;
+                                case HttpNegotiateConstants.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS:
+                                    status = NetError.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
+                                    break;
+                                case HttpNegotiateConstants.ERR_INVALID_RESPONSE:
+                                    status = NetError.ERR_INVALID_RESPONSE;
+                                    break;
+                                case HttpNegotiateConstants.ERR_INVALID_AUTH_CREDENTIALS:
+                                    status = NetError.ERR_INVALID_AUTH_CREDENTIALS;
+                                    break;
+                                case HttpNegotiateConstants.ERR_UNSUPPORTED_AUTH_SCHEME:
+                                    status = NetError.ERR_UNSUPPORTED_AUTH_SCHEME;
+                                    break;
+                                case HttpNegotiateConstants.ERR_MISSING_AUTH_CREDENTIALS:
+                                    status = NetError.ERR_MISSING_AUTH_CREDENTIALS;
+                                    break;
+                                case HttpNegotiateConstants
+                                        .ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS:
+                                    status = NetError.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
+                                    break;
+                                case HttpNegotiateConstants.ERR_MALFORMED_IDENTITY:
+                                    status = NetError.ERR_MALFORMED_IDENTITY;
+                                    break;
+                                default:
+                                    status = NetError.ERR_UNEXPECTED;
+                            }
+                            nativeSetResult(nativeResultObject, status,
+                                    result.getString(AccountManager.KEY_AUTHTOKEN));
+                        } catch (OperationCanceledException | AuthenticatorException
+                                | IOException e) {
+                            nativeSetResult(nativeResultObject, NetError.ERR_ABORTED, null);
+                        }
+                    }
+
+                }, new Handler(ThreadUtils.getUiThreadLooper()));
+    }
+
+    @VisibleForTesting
+    native void nativeSetResult(
+            long nativeJavaNegotiateResultWrapper, int status, String authToken);
+}
diff --git a/net/android/java/src/org/chromium/net/HttpNegotiateConstants.java b/net/android/java/src/org/chromium/net/HttpNegotiateConstants.java
new file mode 100644
index 0000000..fa17647
--- /dev/null
+++ b/net/android/java/src/org/chromium/net/HttpNegotiateConstants.java
@@ -0,0 +1,51 @@
+// Copyright 2015 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.net;
+
+/**
+ * Constants used by Chrome in SPNEGO authentication requests to the Android Account Manager.
+ */
+public class HttpNegotiateConstants {
+    // Option bundle keys
+    //
+    // The token provided by in the HTTP 401 response (Base64 encoded string)
+    public static final String KEY_INCOMING_AUTH_TOKEN = "incomingAuthToken";
+    // The SPNEGO Context from the previous transaction (Bundle) - also used in the response bundle
+    public static final String KEY_SPNEGO_CONTEXT = "spnegoContext";
+    // True if delegation is allowed
+    public static final String KEY_CAN_DELEGATE = "canDelegate";
+
+    // Response bundle keys
+    //
+    // The returned status from the authenticator.
+    public static final String KEY_SPNEGO_RESULT = "spnegoResult";
+
+    // Name of SPNEGO feature
+    public static final String SPNEGO_FEATURE = "SPNEGO";
+    // Prefix of token type. Full token type is "SPNEGO:HOSTBASED:<spn>"
+    public static final String SPNEGO_TOKEN_TYPE_BASE = "SPNEGO:HOSTBASED:";
+
+    // Returned status codes
+    // All OK. Returned token is valid.
+    public static final int OK = 0;
+    // An unexpected error. This may be caused by a programming mistake or an invalid assumption.
+    public static final int ERR_UNEXPECTED = 1;
+    // Request aborted due to user action.
+    public static final int ERR_ABORTED = 2;
+    // An unexpected, but documented, SSPI or GSSAPI status code was returned.
+    public static final int ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS = 3;
+    // The server's response was invalid.
+    public static final int ERR_INVALID_RESPONSE = 4;
+    // Credentials could not be established during HTTP Authentication.
+    public static final int ERR_INVALID_AUTH_CREDENTIALS = 5;
+    // An HTTP Authentication scheme was tried which is not supported on this machine.
+    public static final int ERR_UNSUPPORTED_AUTH_SCHEME = 6;
+    // (GSSAPI) No Kerberos credentials were available during HTTP Authentication.
+    public static final int ERR_MISSING_AUTH_CREDENTIALS = 7;
+    // An undocumented SSPI or GSSAPI status code was returned.
+    public static final int ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS = 8;
+    // The identity used for authentication is invalid.
+    public static final int ERR_MALFORMED_IDENTITY = 9;
+}
diff --git a/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java b/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java
new file mode 100644
index 0000000..8647407
--- /dev/null
+++ b/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java
@@ -0,0 +1,222 @@
+// Copyright 2015 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.net;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+
+import org.chromium.base.BaseChromiumApplication;
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowAccountManager;
+
+import java.io.IOException;
+
+/**
+ * Robolectric tests for HttpNegotiateAuthenticator
+ */
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE,
+        shadows = HttpNegotiateAuthenticatorTest.ExtendedShadowAccountManager.class,
+        application = BaseChromiumApplication.class)
+public class HttpNegotiateAuthenticatorTest {
+    static int sCallCount = 0;
+    static String sAccountTypeReceived;
+    static String sAuthTokenTypeReceived;
+    static String sFeaturesReceived[];
+    static Bundle sAddAccountOptionsReceived;
+    static Bundle sAuthTokenOptionsReceived;
+    static AccountManagerCallback<Bundle> sCallbackReceived;
+    static Handler sHandlerReceived;
+
+    /**
+     * Robolectic's ShadowAccountManager doesn't implement getAccountsByTypeAndFeature so extend it.
+     * We simply check the call is correct, and don't try to emulate it Note: Shadow classes need to
+     * be public and static.
+     */
+    @Implements(AccountManager.class)
+    public static class ExtendedShadowAccountManager extends ShadowAccountManager {
+        @Implementation
+        public AccountManagerFuture<Bundle> getAuthTokenByFeatures(String accountType,
+                String authTokenType, String[] features, Activity activity,
+                Bundle addAccountOptions, Bundle getAuthTokenOptions,
+                AccountManagerCallback<Bundle> callback, Handler handler) {
+            sCallCount++;
+            sAccountTypeReceived = accountType;
+            sAuthTokenTypeReceived = authTokenType;
+            sFeaturesReceived = features;
+            sAddAccountOptionsReceived = addAccountOptions;
+            sAuthTokenOptionsReceived = getAuthTokenOptions;
+            sCallbackReceived = callback;
+            sHandlerReceived = handler;
+
+            return null;
+        }
+    }
+
+    /**
+     * Test of {@link HttpNegotiateAuthenticator#getNextAuthToken}
+     */
+    @Test
+    public void testGetNextAuthToken() {
+        HttpNegotiateAuthenticator authenticator =
+                HttpNegotiateAuthenticator.create("Dummy_Account");
+        Robolectric.buildActivity(Activity.class).create().start().resume().visible();
+        authenticator.getNextAuthToken(0, "test_principal", "", true);
+        assertThat("getAuthTokenByFeatures called precisely once", sCallCount, equalTo(1));
+        assertThat("Received account type matches input", sAccountTypeReceived,
+                equalTo("Dummy_Account"));
+        assertThat("AuthTokenType is \"SPNEGO:HOSTBASED:test_principal\"", sAuthTokenTypeReceived,
+                equalTo("SPNEGO:HOSTBASED:test_principal"));
+        assertThat("Features are precisely {\"SPNEGO\"}", sFeaturesReceived,
+                equalTo(new String[] {"SPNEGO"}));
+        assertThat("No account options requested", sAddAccountOptionsReceived, nullValue());
+        assertThat("There is no existing context",
+                sAuthTokenOptionsReceived.get(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT),
+                nullValue());
+        assertThat("The existing token is empty",
+                sAuthTokenOptionsReceived.getString(HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN),
+                equalTo(""));
+        assertThat("Delegation is allowed",
+                sAuthTokenOptionsReceived.getBoolean(HttpNegotiateConstants.KEY_CAN_DELEGATE),
+                equalTo(true));
+        assertThat("getAuthTokenByFeatures was called with a callback", sCallbackReceived,
+                notNullValue());
+        assertThat("getAuthTokenByFeatures was called with a handler", sHandlerReceived,
+                notNullValue());
+    }
+
+    /**
+     * Test of callback called when getting the auth token completes.
+     */
+    @Test
+    public void testAccountManagerCallbackRun() {
+        // Spy on the authenticator so that we can override and intercept the native method call.
+        HttpNegotiateAuthenticator authenticator =
+                spy(HttpNegotiateAuthenticator.create("Dummy_Account"));
+        doNothing().when(authenticator).nativeSetResult(anyLong(), anyInt(), anyString());
+
+        Robolectric.buildActivity(Activity.class).create().start().resume().visible();
+
+        // Call getNextAuthToken to get the callback
+        authenticator.getNextAuthToken(1234, "test_principal", "", true);
+
+        // Avoid warning when creating mock accountManagerFuture, can't take .class of an
+        // instantiated generic type, yet compiler complains if I leave it uninstantiated.
+        @SuppressWarnings("unchecked")
+        AccountManagerFuture<Bundle> accountManagerFuture = mock(AccountManagerFuture.class);
+        Bundle resultBundle = new Bundle();
+        Bundle context = new Bundle();
+        context.putString("String", "test_context");
+        resultBundle.putInt(HttpNegotiateConstants.KEY_SPNEGO_RESULT, HttpNegotiateConstants.OK);
+        resultBundle.putBundle(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT, context);
+        resultBundle.putString(AccountManager.KEY_AUTHTOKEN, "output_token");
+        try {
+            when(accountManagerFuture.getResult()).thenReturn(resultBundle);
+        } catch (OperationCanceledException | AuthenticatorException | IOException e) {
+            // Can never happen - artifact of Mockito.
+            fail();
+        }
+        sCallbackReceived.run(accountManagerFuture);
+        verify(authenticator).nativeSetResult(1234, 0, "output_token");
+
+        // Check that the next call to getNextAuthToken uses the correct context
+        authenticator.getNextAuthToken(5678, "test_principal", "", true);
+        assertThat("The spnego context is preserved between calls",
+                sAuthTokenOptionsReceived.getBundle(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT),
+                equalTo(context));
+
+        // Test exception path
+        try {
+            when(accountManagerFuture.getResult()).thenThrow(new OperationCanceledException());
+        } catch (OperationCanceledException | AuthenticatorException | IOException e) {
+            // Can never happen - artifact of Mockito.
+            fail();
+        }
+        sCallbackReceived.run(accountManagerFuture);
+        verify(authenticator).nativeSetResult(5678, NetError.ERR_ABORTED, null);
+    }
+
+    private void checkErrorReturn(Integer spnegoError, int expectedError) {
+        // Spy on the authenticator so that we can override and intercept the native method call.
+        HttpNegotiateAuthenticator authenticator =
+                spy(HttpNegotiateAuthenticator.create("Dummy_Account"));
+        doNothing().when(authenticator).nativeSetResult(anyLong(), anyInt(), anyString());
+
+        Robolectric.buildActivity(Activity.class).create().start().resume().visible();
+
+        // Call getNextAuthToken to get the callback
+        authenticator.getNextAuthToken(1234, "test_principal", "", true);
+
+        // Avoid warning when creating mock accountManagerFuture, can't take .class of an
+        // instantiated generic type, yet compiler complains if I leave it uninstantiated.
+        @SuppressWarnings("unchecked")
+        AccountManagerFuture<Bundle> accountManagerFuture = mock(AccountManagerFuture.class);
+        Bundle resultBundle = new Bundle();
+        if (spnegoError != null) {
+            resultBundle.putInt(HttpNegotiateConstants.KEY_SPNEGO_RESULT, spnegoError);
+        }
+        try {
+            when(accountManagerFuture.getResult()).thenReturn(resultBundle);
+        } catch (OperationCanceledException | AuthenticatorException | IOException e) {
+            // Can never happen - artifact of Mockito.
+            fail();
+        }
+        sCallbackReceived.run(accountManagerFuture);
+        verify(authenticator).nativeSetResult(anyLong(), eq(expectedError), anyString());
+    }
+
+    /**
+     * Test of callback error returns when getting the auth token completes.
+     */
+    @Test
+    public void testAccountManagerCallbackErrorReturns() {
+        checkErrorReturn(null, NetError.ERR_UNEXPECTED);
+        checkErrorReturn(HttpNegotiateConstants.ERR_UNEXPECTED, NetError.ERR_UNEXPECTED);
+        checkErrorReturn(HttpNegotiateConstants.ERR_ABORTED, NetError.ERR_ABORTED);
+        checkErrorReturn(HttpNegotiateConstants.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS,
+                NetError.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS);
+        checkErrorReturn(
+                HttpNegotiateConstants.ERR_INVALID_RESPONSE, NetError.ERR_INVALID_RESPONSE);
+        checkErrorReturn(HttpNegotiateConstants.ERR_INVALID_AUTH_CREDENTIALS,
+                NetError.ERR_INVALID_AUTH_CREDENTIALS);
+        checkErrorReturn(HttpNegotiateConstants.ERR_UNSUPPORTED_AUTH_SCHEME,
+                NetError.ERR_UNSUPPORTED_AUTH_SCHEME);
+        checkErrorReturn(HttpNegotiateConstants.ERR_MISSING_AUTH_CREDENTIALS,
+                NetError.ERR_MISSING_AUTH_CREDENTIALS);
+        checkErrorReturn(HttpNegotiateConstants.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS,
+                NetError.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS);
+        checkErrorReturn(
+                HttpNegotiateConstants.ERR_MALFORMED_IDENTITY, NetError.ERR_MALFORMED_IDENTITY);
+        // 9999 is not a valid return value
+        checkErrorReturn(9999, NetError.ERR_UNEXPECTED);
+    }
+}
diff --git a/net/android/net_jni_registrar.cc b/net/android/net_jni_registrar.cc
index 1175d96..dec74ecc1 100644
--- a/net/android/net_jni_registrar.cc
+++ b/net/android/net_jni_registrar.cc
@@ -8,6 +8,7 @@
 #include "base/android/jni_registrar.h"
 #include "net/android/android_private_key.h"
 #include "net/android/gurl_utils.h"
+#include "net/android/http_auth_negotiate_android.h"
 #include "net/android/keystore.h"
 #include "net/android/network_change_notifier_android.h"
 #include "net/android/network_library.h"
@@ -27,6 +28,7 @@
     {"AndroidKeyStore", RegisterKeyStore},
     {"AndroidNetworkLibrary", RegisterNetworkLibrary},
     {"GURLUtils", RegisterGURLUtils},
+    {"HttpAuthNegotiateAndroid", HttpAuthNegotiateAndroid::Register},
     {"NetworkChangeNotifierAndroid", NetworkChangeNotifierAndroid::Register},
     {"ProxyConfigService", ProxyConfigServiceAndroid::Register},
     {"X509Util", RegisterX509Util},
diff --git a/net/android/unittest_support/AndroidManifest.xml b/net/android/unittest_support/AndroidManifest.xml
new file mode 100644
index 0000000..d7493de
--- /dev/null
+++ b/net/android/unittest_support/AndroidManifest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="org.chromium.native_test"
+      android:versionCode="1"
+      android:versionName="1.0">
+
+    <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="22" />
+    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+    <application android:label="NativeTests"
+            android:name="org.chromium.base.BaseChromiumApplication">
+        <activity android:name=".NativeUnitTestActivity"
+                android:label="NativeTest"
+                android:configChanges="orientation|keyboardHidden">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <service android:name="org.chromium.net.test.DummySpnegoAuthenticatorService"
+                android:exported = "false">
+            <intent-filter>
+                <action android:name="android.accounts.AccountAuthenticator" />
+            </intent-filter>
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                     android:resource="@xml/dummy_spnego_authenticator" />
+        </service>
+    </application>
+
+    <instrumentation android:name="org.chromium.native_test.NativeTestInstrumentationTestRunner"
+            android:targetPackage="org.chromium.native_test"
+            android:label="Instrumentation entry point for org.chromium.native_test"/>
+
+</manifest>
diff --git a/net/android/unittest_support/res/mipmap-hdpi/app_icon.png b/net/android/unittest_support/res/mipmap-hdpi/app_icon.png
new file mode 100644
index 0000000..da881485
--- /dev/null
+++ b/net/android/unittest_support/res/mipmap-hdpi/app_icon.png
Binary files differ
diff --git a/net/android/unittest_support/res/mipmap-mdpi/app_icon.png b/net/android/unittest_support/res/mipmap-mdpi/app_icon.png
new file mode 100644
index 0000000..24611dec
--- /dev/null
+++ b/net/android/unittest_support/res/mipmap-mdpi/app_icon.png
Binary files differ
diff --git a/net/android/unittest_support/res/mipmap-xhdpi/app_icon.png b/net/android/unittest_support/res/mipmap-xhdpi/app_icon.png
new file mode 100644
index 0000000..98c91899
--- /dev/null
+++ b/net/android/unittest_support/res/mipmap-xhdpi/app_icon.png
Binary files differ
diff --git a/net/android/unittest_support/res/mipmap-xxhdpi/app_icon.png b/net/android/unittest_support/res/mipmap-xxhdpi/app_icon.png
new file mode 100644
index 0000000..1fbc0e7
--- /dev/null
+++ b/net/android/unittest_support/res/mipmap-xxhdpi/app_icon.png
Binary files differ
diff --git a/net/android/unittest_support/res/mipmap-xxxhdpi/app_icon.png b/net/android/unittest_support/res/mipmap-xxxhdpi/app_icon.png
new file mode 100644
index 0000000..5b09d42
--- /dev/null
+++ b/net/android/unittest_support/res/mipmap-xxxhdpi/app_icon.png
Binary files differ
diff --git a/net/android/unittest_support/res/xml/dummy_spnego_account_preferences.xml b/net/android/unittest_support/res/xml/dummy_spnego_account_preferences.xml
new file mode 100644
index 0000000..38901dce
--- /dev/null
+++ b/net/android/unittest_support/res/xml/dummy_spnego_account_preferences.xml
@@ -0,0 +1 @@
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"/>
diff --git a/net/android/unittest_support/res/xml/dummy_spnego_authenticator.xml b/net/android/unittest_support/res/xml/dummy_spnego_authenticator.xml
new file mode 100644
index 0000000..aab1b93b
--- /dev/null
+++ b/net/android/unittest_support/res/xml/dummy_spnego_authenticator.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+                       android:accountType="org.chromium.test.DummySpnegoAuthenticator"
+                       android:label="DummySpengoAuthenticator" 
+                       android:icon="@mipmap/app_icon"
+                       android:smallIcon="@mipmap/app_icon"
+                       android:customTokens="true"
+                       android:accountPreferences="@xml/dummy_spnego_account_preferences"/>
diff --git a/net/http/http_auth_gssapi_posix.cc b/net/http/http_auth_gssapi_posix.cc
index 388cc64..0b87b33 100644
--- a/net/http/http_auth_gssapi_posix.cc
+++ b/net/http/http_auth_gssapi_posix.cc
@@ -16,7 +16,7 @@
 #include "base/threading/thread_restrictions.h"
 #include "net/base/net_errors.h"
 #include "net/base/net_util.h"
-#include "net/http/http_auth_challenge_tokenizer.h"
+#include "net/http/http_auth_multi_round_parse.h"
 
 // These are defined for the GSSAPI library:
 // Paraphrasing the comments from gssapi.h:
@@ -687,39 +687,18 @@
 
 HttpAuth::AuthorizationResult HttpAuthGSSAPI::ParseChallenge(
     HttpAuthChallengeTokenizer* tok) {
-  // Verify the challenge's auth-scheme.
-  if (!base::LowerCaseEqualsASCII(tok->scheme(),
-                                  base::StringToLowerASCII(scheme_).c_str()))
-    return HttpAuth::AUTHORIZATION_RESULT_INVALID;
-
-  std::string encoded_auth_token = tok->base64_param();
-
-  if (encoded_auth_token.empty()) {
-    // If a context has already been established, an empty Negotiate challenge
-    // should be treated as a rejection of the current attempt.
-    if (scoped_sec_context_.get() != GSS_C_NO_CONTEXT)
-      return HttpAuth::AUTHORIZATION_RESULT_REJECT;
-    DCHECK(decoded_server_auth_token_.empty());
-    return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
-  } else {
-    // If a context has not already been established, additional tokens should
-    // not be present in the auth challenge.
-    if (scoped_sec_context_.get() == GSS_C_NO_CONTEXT)
-      return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+  if (scoped_sec_context_.get() == GSS_C_NO_CONTEXT) {
+    return net::ParseFirstRoundChallenge(scheme_, tok);
   }
-
-  // Make sure the additional token is base64 encoded.
-  std::string decoded_auth_token;
-  bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
-  if (!base64_rv)
-    return HttpAuth::AUTHORIZATION_RESULT_INVALID;
-  decoded_server_auth_token_ = decoded_auth_token;
-  return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+  std::string encoded_auth_token;
+  return net::ParseLaterRoundChallenge(scheme_, tok, &encoded_auth_token,
+                                       &decoded_server_auth_token_);
 }
 
 int HttpAuthGSSAPI::GenerateAuthToken(const AuthCredentials* credentials,
                                       const std::string& spn,
-                                      std::string* auth_token) {
+                                      std::string* auth_token,
+                                      const CompletionCallback& /*callback*/) {
   DCHECK(auth_token);
 
   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
diff --git a/net/http/http_auth_gssapi_posix.h b/net/http/http_auth_gssapi_posix.h
index ab967c96..73502c9 100644
--- a/net/http/http_auth_gssapi_posix.h
+++ b/net/http/http_auth_gssapi_posix.h
@@ -9,6 +9,7 @@
 
 #include "base/gtest_prod_util.h"
 #include "base/native_library.h"
+#include "net/base/completion_callback.h"
 #include "net/base/net_export.h"
 #include "net/http/http_auth.h"
 
@@ -22,7 +23,7 @@
 
 // Chrome supports OSX 10.6, which doesn't have access to GSS.framework. Chrome
 // always dlopens libgssapi_krb5.dylib, which is provided by
-// Kerberos.framework. On OSX 10.7+ this is an ABI comptabile shim that loads
+// Kerberos.framework. On OSX 10.7+ this is an ABI compatible shim that loads
 // GSS.framework.
 #include <Kerberos/gssapi.h>
 #elif defined(OS_FREEBSD)
@@ -246,19 +247,35 @@
       HttpAuthChallengeTokenizer* tok);
 
   // Generates an authentication token.
-  // The return value is an error code. If it's not |OK|, the value of
+  //
+  // The return value is an error code. The authentication token will be
+  // returned in |*auth_token|. If the result code is not |OK|, the value of
   // |*auth_token| is unspecified.
+  //
+  // If the operation cannot be completed synchronously, |ERR_IO_PENDING| will
+  // be returned and the real result code will be passed to the completion
+  // callback.  Otherwise the result code is returned immediately from this
+  // call.
+  //
+  // If the HttpAuthGSSAPI object is deleted before completion then the callback
+  // will not be called.
+  //
+  // If no immediate result is returned then |auth_token| must remain valid
+  // until the callback has been called.
+  //
   // |spn| is the Service Principal Name of the server that the token is
   // being generated for.
+  //
   // If this is the first round of a multiple round scheme, credentials are
   // obtained using |*credentials|. If |credentials| is NULL, the default
   // credentials are used instead.
   int GenerateAuthToken(const AuthCredentials* credentials,
                         const std::string& spn,
-                        std::string* auth_token);
+                        std::string* auth_token,
+                        const CompletionCallback& callback);
 
   // Delegation is allowed on the Kerberos ticket. This allows certain servers
-  // to act as the user, such as an IIS server retrieiving data from a
+  // to act as the user, such as an IIS server retrieving data from a
   // Kerberized MSSQL server.
   void Delegate();
 
diff --git a/net/http/http_auth_gssapi_posix_unittest.cc b/net/http/http_auth_gssapi_posix_unittest.cc
index 6f93334..79a248f 100644
--- a/net/http/http_auth_gssapi_posix_unittest.cc
+++ b/net/http/http_auth_gssapi_posix_unittest.cc
@@ -71,6 +71,12 @@
       out_buffer);
 }
 
+void UnexpectedCallback(int result) {
+  // At present getting tokens from gssapi is fully synchronous, so the callback
+  // should never be called.
+  ADD_FAILURE();
+}
+
 }  // namespace
 
 TEST(HttpAuthGSSAPIPOSIXTest, GSSAPIStartup) {
@@ -204,7 +210,8 @@
   EstablishInitialContext(&mock_library);
   std::string auth_token;
   EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, "HTTP/intranet.google.com",
-                                              &auth_token));
+                                              &auth_token,
+                                              base::Bind(&UnexpectedCallback)));
 
   std::string second_challenge_text = "Negotiate Zm9vYmFy";
   HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
@@ -241,7 +248,8 @@
   EstablishInitialContext(&mock_library);
   std::string auth_token;
   EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, "HTTP/intranet.google.com",
-                                              &auth_token));
+                                              &auth_token,
+                                              base::Bind(&UnexpectedCallback)));
   std::string second_challenge_text = "Negotiate";
   HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
                                               second_challenge_text.end());
@@ -264,7 +272,8 @@
   EstablishInitialContext(&mock_library);
   std::string auth_token;
   EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, "HTTP/intranet.google.com",
-                                              &auth_token));
+                                              &auth_token,
+                                              base::Bind(&UnexpectedCallback)));
   std::string second_challenge_text = "Negotiate =happyjoy=";
   HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
                                               second_challenge_text.end());
diff --git a/net/http/http_auth_handler_factory.cc b/net/http/http_auth_handler_factory.cc
index decb20e..4f09c9f 100644
--- a/net/http/http_auth_handler_factory.cc
+++ b/net/http/http_auth_handler_factory.cc
@@ -53,7 +53,9 @@
   registry_factory->RegisterSchemeFactory(
       "digest", new HttpAuthHandlerDigest::Factory());
 
-#if defined(USE_KERBEROS)
+// On Android Chrome needs an account type configured to enable Kerberos,
+// so the default factory should not include Kerberos.
+#if defined(USE_KERBEROS) && !defined(OS_ANDROID)
   HttpAuthHandlerNegotiate::Factory* negotiate_factory =
       new HttpAuthHandlerNegotiate::Factory();
 #if defined(OS_POSIX)
@@ -63,7 +65,7 @@
 #endif
   negotiate_factory->set_host_resolver(host_resolver);
   registry_factory->RegisterSchemeFactory("negotiate", negotiate_factory);
-#endif  // defined(USE_KERBEROS)
+#endif  // defined(USE_KERBEROS) && !defined(OS_ANDROID)
 
   HttpAuthHandlerNTLM::Factory* ntlm_factory =
       new HttpAuthHandlerNTLM::Factory();
@@ -131,6 +133,7 @@
     URLSecurityManager* security_manager,
     HostResolver* host_resolver,
     const std::string& gssapi_library_name,
+    const std::string& auth_android_negotiate_account_type,
     bool negotiate_disable_cname_lookup,
     bool negotiate_enable_port) {
   HttpAuthHandlerRegistryFactory* registry_factory =
@@ -154,7 +157,9 @@
   if (IsSupportedScheme(supported_schemes, "negotiate")) {
     HttpAuthHandlerNegotiate::Factory* negotiate_factory =
         new HttpAuthHandlerNegotiate::Factory();
-#if defined(OS_POSIX)
+#if defined(OS_ANDROID)
+    negotiate_factory->set_library(&auth_android_negotiate_account_type);
+#elif defined(OS_POSIX)
     negotiate_factory->set_library(
         new GSSAPISharedLibrary(gssapi_library_name));
 #elif defined(OS_WIN)
diff --git a/net/http/http_auth_handler_factory.h b/net/http/http_auth_handler_factory.h
index 06f7f34..30b1896c 100644
--- a/net/http/http_auth_handler_factory.h
+++ b/net/http/http_auth_handler_factory.h
@@ -169,7 +169,12 @@
   // |host_resolver| must not be NULL.
   //
   // |gssapi_library_name| specifies the name of the GSSAPI library that will
-  // be loaded on all platforms except Windows.
+  // be loaded on Posix platforms other than Android. |gssapi_library_name| is
+  // ignored on Android and Windows.
+  //
+  // |auth_android_negotiate_account_type| is an Android account type, used to
+  // find the appropriate authenticator service on Android. It is ignored on
+  // non-Android platforms.
   //
   // |negotiate_disable_cname_lookup| and |negotiate_enable_port| both control
   // how Negotiate does SPN generation, by default these should be false.
@@ -178,6 +183,7 @@
       URLSecurityManager* security_manager,
       HostResolver* host_resolver,
       const std::string& gssapi_library_name,
+      const std::string& auth_android_negotiate_account_type,
       bool negotiate_disable_cname_lookup,
       bool negotiate_enable_port);
 
diff --git a/net/http/http_auth_handler_factory_unittest.cc b/net/http/http_auth_handler_factory_unittest.cc
index 2aa79588..be0c3b0 100644
--- a/net/http/http_auth_handler_factory_unittest.cc
+++ b/net/http/http_auth_handler_factory_unittest.cc
@@ -172,7 +172,8 @@
         server_origin,
         BoundNetLog(),
         &handler);
-#if defined(USE_KERBEROS)
+// Note the default factory doesn't support Kerberos on Android
+#if defined(USE_KERBEROS) && !defined(OS_ANDROID)
     EXPECT_EQ(OK, rv);
     ASSERT_FALSE(handler.get() == NULL);
     EXPECT_EQ(HttpAuth::AUTH_SCHEME_NEGOTIATE, handler->auth_scheme());
@@ -183,7 +184,7 @@
 #else
     EXPECT_EQ(ERR_UNSUPPORTED_AUTH_SCHEME, rv);
     EXPECT_TRUE(handler.get() == NULL);
-#endif  // defined(USE_KERBEROS)
+#endif  // defined(USE_KERBEROS) && !defined(OS_ANDROID)
   }
 }
 
diff --git a/net/http/http_auth_handler_negotiate.cc b/net/http/http_auth_handler_negotiate.cc
index 422ddd7..b02913b 100644
--- a/net/http/http_auth_handler_negotiate.cc
+++ b/net/http/http_auth_handler_negotiate.cc
@@ -61,10 +61,14 @@
       new HttpAuthHandlerNegotiate(auth_library_.get(), max_token_length_,
                                    url_security_manager(), resolver_,
                                    disable_cname_lookup_, use_port_));
-  if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
-    return ERR_INVALID_RESPONSE;
-  handler->swap(tmp_handler);
-  return OK;
+#elif defined(OS_ANDROID)
+  if (is_unsupported_ || auth_library_->empty() || reason == CREATE_PREEMPTIVE)
+    return ERR_UNSUPPORTED_AUTH_SCHEME;
+  // TODO(cbentzel): Move towards model of parsing in the factory
+  //                 method and only constructing when valid.
+  scoped_ptr<HttpAuthHandler> tmp_handler(new HttpAuthHandlerNegotiate(
+      auth_library_.get(), url_security_manager(), resolver_,
+      disable_cname_lookup_, use_port_));
 #elif defined(OS_POSIX)
   if (is_unsupported_)
     return ERR_UNSUPPORTED_AUTH_SCHEME;
@@ -78,11 +82,11 @@
       new HttpAuthHandlerNegotiate(auth_library_.get(), url_security_manager(),
                                    resolver_, disable_cname_lookup_,
                                    use_port_));
+#endif
   if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
     return ERR_INVALID_RESPONSE;
   handler->swap(tmp_handler);
   return OK;
-#endif
 }
 
 HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate(
@@ -94,7 +98,9 @@
     HostResolver* resolver,
     bool disable_cname_lookup,
     bool use_port)
-#if defined(OS_WIN)
+#if defined(OS_ANDROID)
+    : auth_system_(*auth_library),
+#elif defined(OS_WIN)
     : auth_system_(auth_library, "Negotiate", NEGOSSP_NAME, max_token_length),
 #elif defined(OS_POSIX)
     : auth_system_(auth_library, "Negotiate", CHROME_GSS_SPNEGO_MECH_OID_DESC),
@@ -315,8 +321,10 @@
 int HttpAuthHandlerNegotiate::DoGenerateAuthToken() {
   next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
   AuthCredentials* credentials = has_credentials_ ? &credentials_ : NULL;
-  // TODO(cbentzel): This should possibly be done async.
-  return auth_system_.GenerateAuthToken(credentials, spn_, auth_token_);
+  return auth_system_.GenerateAuthToken(
+      credentials, spn_, auth_token_,
+      base::Bind(&HttpAuthHandlerNegotiate::OnIOComplete,
+                 base::Unretained(this)));
 }
 
 int HttpAuthHandlerNegotiate::DoGenerateAuthTokenComplete(int rv) {
diff --git a/net/http/http_auth_handler_negotiate.h b/net/http/http_auth_handler_negotiate.h
index fc596fa..d8b9d9d 100644
--- a/net/http/http_auth_handler_negotiate.h
+++ b/net/http/http_auth_handler_negotiate.h
@@ -13,7 +13,9 @@
 #include "net/http/http_auth_handler.h"
 #include "net/http/http_auth_handler_factory.h"
 
-#if defined(OS_WIN)
+#if defined(OS_ANDROID)
+#include "net/android/http_auth_negotiate_android.h"
+#elif defined(OS_WIN)
 #include "net/http/http_auth_sspi_win.h"
 #elif defined(OS_POSIX)
 #include "net/http/http_auth_gssapi_posix.h"
@@ -32,7 +34,12 @@
 
 class NET_EXPORT_PRIVATE HttpAuthHandlerNegotiate : public HttpAuthHandler {
  public:
-#if defined(OS_WIN)
+#if defined(OS_ANDROID)
+  typedef net::android::HttpAuthNegotiateAndroid AuthSystem;
+  // For Android this isn't a library, but for the Android Account type, which
+  // indirectly identifies the Kerberos/SPNEGO authentication app.
+  typedef const std::string AuthLibrary;
+#elif defined(OS_WIN)
   typedef SSPILibrary AuthLibrary;
   typedef HttpAuthSSPI AuthSystem;
 #elif defined(OS_POSIX)
@@ -65,8 +72,8 @@
 
     // Sets the system library to use, thereby assuming ownership of
     // |auth_library|.
-    void set_library(AuthLibrary* auth_library) {
-      auth_library_.reset(auth_library);
+    void set_library(AuthLibrary* auth_provider) {
+      auth_library_.reset(auth_provider);
     }
 
     int CreateAuthHandler(HttpAuthChallengeTokenizer* challenge,
@@ -89,7 +96,7 @@
     scoped_ptr<AuthLibrary> auth_library_;
   };
 
-  HttpAuthHandlerNegotiate(AuthLibrary* sspi_library,
+  HttpAuthHandlerNegotiate(AuthLibrary* auth_library,
 #if defined(OS_WIN)
                            ULONG max_token_length,
 #endif
diff --git a/net/http/http_auth_handler_negotiate_unittest.cc b/net/http/http_auth_handler_negotiate_unittest.cc
index eaee8e5..49ebdad6 100644
--- a/net/http/http_auth_handler_negotiate_unittest.cc
+++ b/net/http/http_auth_handler_negotiate_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "net/http/http_auth_handler_negotiate.h"
 
+#include <string>
+
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "net/base/net_errors.h"
@@ -11,7 +13,9 @@
 #include "net/dns/mock_host_resolver.h"
 #include "net/http/http_request_info.h"
 #include "net/http/mock_allow_url_security_manager.h"
-#if defined(OS_WIN)
+#if defined(OS_ANDROID)
+#include "net/android/dummy_spnego_authenticator.h"
+#elif defined(OS_WIN)
 #include "net/http/mock_sspi_library_win.h"
 #elif defined(OS_POSIX)
 #include "net/http/mock_gssapi_library_posix.h"
@@ -21,7 +25,9 @@
 
 namespace net {
 
-#if defined(OS_WIN)
+#if defined(OS_ANDROID)
+typedef net::android::DummySpnegoAuthenticator MockAuthLibrary;
+#elif defined(OS_WIN)
 typedef MockSSPILibrary MockAuthLibrary;
 #elif defined(OS_POSIX)
 typedef test::MockGSSAPILibrary MockAuthLibrary;
@@ -38,10 +44,22 @@
     url_security_manager_.reset(new MockAllowURLSecurityManager());
     factory_.reset(new HttpAuthHandlerNegotiate::Factory());
     factory_->set_url_security_manager(url_security_manager_.get());
+#if defined(OS_ANDROID)
+    std::string* authenticator =
+        new std::string("org.chromium.test.DummySpnegoAuthenticator");
+    factory_->set_library(authenticator);
+    MockAuthLibrary::EnsureTestAccountExists();
+#endif
+#if defined(OS_WIN) || (defined(OS_POSIX) && !defined(OS_ANDROID))
     factory_->set_library(auth_library_);
+#endif
     factory_->set_host_resolver(resolver_.get());
   }
 
+#if defined(OS_ANDROID)
+  void TearDown() override { MockAuthLibrary::RemoveTestAccounts(); }
+#endif
+
   void SetupMocks(MockAuthLibrary* mock_library) {
 #if defined(OS_WIN)
     security_package_.reset(new SecPkgInfoW);
@@ -113,21 +131,21 @@
         0,                                   // Context flags
         1,                                   // Locally initiated
         1);                                  // Open
-    test::MockGSSAPILibrary::SecurityContextQuery queries[] = {
-    test::MockGSSAPILibrary::SecurityContextQuery(
-        "Negotiate",                    // Package name
-        GSS_S_CONTINUE_NEEDED,          // Major response code
-        0,                              // Minor response code
-        context1,                       // Context
-        NULL,                           // Expected input token
-        kAuthResponse),                 // Output token
-    test::MockGSSAPILibrary::SecurityContextQuery(
-        "Negotiate",                    // Package name
-        GSS_S_COMPLETE,                 // Major response code
-        0,                              // Minor response code
-        context2,                       // Context
-        kAuthResponse,                  // Expected input token
-        kAuthResponse)                  // Output token
+    MockAuthLibrary::SecurityContextQuery queries[] = {
+        MockAuthLibrary::SecurityContextQuery(
+            "Negotiate",            // Package name
+            GSS_S_CONTINUE_NEEDED,  // Major response code
+            0,                      // Minor response code
+            context1,               // Context
+            NULL,                   // Expected input token
+            kAuthResponse),         // Output token
+        MockAuthLibrary::SecurityContextQuery(
+            "Negotiate",     // Package name
+            GSS_S_COMPLETE,  // Major response code
+            0,               // Minor response code
+            context2,        // Context
+            kAuthResponse,   // Expected input token
+            kAuthResponse)   // Output token
     };
 
     for (size_t i = 0; i < arraysize(queries); ++i) {
@@ -154,13 +172,13 @@
         0,                              // Context flags
         1,                              // Locally initiated
         0);                             // Open
-    test::MockGSSAPILibrary::SecurityContextQuery query(
-        "Negotiate",                    // Package name
-        major_status,                   // Major response code
-        minor_status,                   // Minor response code
-        context,                        // Context
-        NULL,                           // Expected input token
-        NULL);                          // Output token
+    MockAuthLibrary::SecurityContextQuery query(
+        "Negotiate",   // Package name
+        major_status,  // Major response code
+        minor_status,  // Minor response code
+        context,       // Context
+        NULL,          // Expected input token
+        NULL);         // Output token
 
     mock_library->ExpectSecurityContext(query.expected_package,
                                         query.response_code,
@@ -223,8 +241,8 @@
   TestCompletionCallback callback;
   HttpRequestInfo request_info;
   std::string token;
-  EXPECT_EQ(OK, auth_handler->GenerateAuthToken(NULL, &request_info,
-                                                callback.callback(), &token));
+  EXPECT_EQ(OK, callback.GetResult(auth_handler->GenerateAuthToken(
+                    NULL, &request_info, callback.callback(), &token)));
 #if defined(OS_WIN)
   EXPECT_EQ("HTTP/alias", auth_handler->spn());
 #elif defined(OS_POSIX)
@@ -241,8 +259,8 @@
   TestCompletionCallback callback;
   HttpRequestInfo request_info;
   std::string token;
-  EXPECT_EQ(OK, auth_handler->GenerateAuthToken(NULL, &request_info,
-                                                callback.callback(), &token));
+  EXPECT_EQ(OK, callback.GetResult(auth_handler->GenerateAuthToken(
+                    NULL, &request_info, callback.callback(), &token)));
 #if defined(OS_WIN)
   EXPECT_EQ("HTTP/alias", auth_handler->spn());
 #elif defined(OS_POSIX)
@@ -259,8 +277,8 @@
   TestCompletionCallback callback;
   HttpRequestInfo request_info;
   std::string token;
-  EXPECT_EQ(OK, auth_handler->GenerateAuthToken(NULL, &request_info,
-                                                callback.callback(), &token));
+  EXPECT_EQ(OK, callback.GetResult(auth_handler->GenerateAuthToken(
+                    NULL, &request_info, callback.callback(), &token)));
 #if defined(OS_WIN)
   EXPECT_EQ("HTTP/alias:500", auth_handler->spn());
 #elif defined(OS_POSIX)
@@ -277,8 +295,8 @@
   TestCompletionCallback callback;
   HttpRequestInfo request_info;
   std::string token;
-  EXPECT_EQ(OK, auth_handler->GenerateAuthToken(NULL, &request_info,
-                                                callback.callback(), &token));
+  EXPECT_EQ(OK, callback.GetResult(auth_handler->GenerateAuthToken(
+                    NULL, &request_info, callback.callback(), &token)));
 #if defined(OS_WIN)
   EXPECT_EQ("HTTP/canonical.example.com", auth_handler->spn());
 #elif defined(OS_POSIX)
diff --git a/net/http/http_auth_handler_ntlm.cc b/net/http/http_auth_handler_ntlm.cc
index 51a32326..0bf7260 100644
--- a/net/http/http_auth_handler_ntlm.cc
+++ b/net/http/http_auth_handler_ntlm.cc
@@ -33,10 +33,8 @@
     const AuthCredentials* credentials, const HttpRequestInfo* request,
     const CompletionCallback& callback, std::string* auth_token) {
 #if defined(NTLM_SSPI)
-  return auth_sspi_.GenerateAuthToken(
-      credentials,
-      CreateSPN(origin_),
-      auth_token);
+  return auth_sspi_.GenerateAuthToken(credentials, CreateSPN(origin_),
+                                      auth_token, callback);
 #else  // !defined(NTLM_SSPI)
   // TODO(cbentzel): Shouldn't be hitting this case.
   if (!credentials) {
diff --git a/net/http/http_auth_multi_round_parse.cc b/net/http/http_auth_multi_round_parse.cc
new file mode 100644
index 0000000..efee2bb
--- /dev/null
+++ b/net/http/http_auth_multi_round_parse.cc
@@ -0,0 +1,58 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/base64.h"
+#include "base/strings/string_util.h"
+#include "net/http/http_auth_challenge_tokenizer.h"
+#include "net/http/http_auth_multi_round_parse.h"
+
+namespace net {
+
+namespace {
+
+// Check that the scheme in the challenge matches the expected scheme
+bool SchemeIsValid(const std::string& scheme,
+                   HttpAuthChallengeTokenizer* challenge) {
+  // There is no guarantee that challenge->scheme() is valid ASCII, but
+  // LowerCaseEqualsASCII will do the right thing even if it isn't.
+  return base::LowerCaseEqualsASCII(challenge->scheme(),
+                                    base::StringToLowerASCII(scheme).c_str());
+}
+
+}  // namespace
+
+HttpAuth::AuthorizationResult ParseFirstRoundChallenge(
+    const std::string& scheme,
+    HttpAuthChallengeTokenizer* challenge) {
+  // Verify the challenge's auth-scheme.
+  if (!SchemeIsValid(scheme, challenge))
+    return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+
+  std::string encoded_auth_token = challenge->base64_param();
+  if (!encoded_auth_token.empty()) {
+    return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+  }
+  return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+}
+
+HttpAuth::AuthorizationResult ParseLaterRoundChallenge(
+    const std::string& scheme,
+    HttpAuthChallengeTokenizer* challenge,
+    std::string* encoded_token,
+    std::string* decoded_token) {
+  // Verify the challenge's auth-scheme.
+  if (!SchemeIsValid(scheme, challenge))
+    return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+
+  *encoded_token = challenge->base64_param();
+  if (encoded_token->empty())
+    return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+
+  // Make sure the additional token is base64 encoded.
+  if (!base::Base64Decode(*encoded_token, decoded_token))
+    return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+  return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+}
+
+}  // namespace net
diff --git a/net/http/http_auth_multi_round_parse.h b/net/http/http_auth_multi_round_parse.h
new file mode 100644
index 0000000..2fb6347
--- /dev/null
+++ b/net/http/http_auth_multi_round_parse.h
@@ -0,0 +1,29 @@
+// Copyright 2015 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 NET_HTTP_HTTP_AUTH_MULTI_ROUND_PARSE_H_
+#define NET_HTTP_HTTP_AUTH_MULTI_ROUND_PARSE_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/http/http_auth.h"
+
+namespace net {
+
+class HttpAuthChallengeTokenizer;
+
+NET_EXPORT_PRIVATE HttpAuth::AuthorizationResult ParseFirstRoundChallenge(
+    const std::string& scheme,
+    HttpAuthChallengeTokenizer* challenge);
+
+NET_EXPORT_PRIVATE HttpAuth::AuthorizationResult ParseLaterRoundChallenge(
+    const std::string& scheme,
+    HttpAuthChallengeTokenizer* challenge,
+    std::string* encoded_token,
+    std::string* decoded_token);
+
+}  // namespace net
+
+#endif  // NET_HTTP_HTTP_AUTH_MULTI_ROUND_PARSE_H_
diff --git a/net/http/http_auth_multi_round_parse_unittest.cc b/net/http/http_auth_multi_round_parse_unittest.cc
new file mode 100644
index 0000000..1be5ad1
--- /dev/null
+++ b/net/http/http_auth_multi_round_parse_unittest.cc
@@ -0,0 +1,78 @@
+// Copyright 2015 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 "net/http/http_auth_challenge_tokenizer.h"
+#include "net/http/http_auth_multi_round_parse.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(HttpAuthHandlerNegotiateParseTest, ParseFirstRoundChallenge) {
+  // The first round should just consist of an unadorned header with the scheme
+  // name.
+  std::string challenge_text = "DummyScheme";
+  HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+                                       challenge_text.end());
+  EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+            ParseFirstRoundChallenge("dummyscheme", &challenge));
+}
+
+TEST(HttpAuthHandlerNegotiateParseTest,
+     ParseFirstNegotiateChallenge_UnexpectedToken) {
+  // If the first round challenge has an additional authentication token, it
+  // should be treated as an invalid challenge from the server.
+  std::string challenge_text = "Negotiate Zm9vYmFy";
+  HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+                                       challenge_text.end());
+  EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+            ParseFirstRoundChallenge("negotiate", &challenge));
+}
+
+TEST(HttpAuthHandlerNegotiateParseTest,
+     ParseFirstNegotiateChallenge_BadScheme) {
+  std::string challenge_text = "DummyScheme";
+  HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+                                       challenge_text.end());
+  EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+            ParseFirstRoundChallenge("negotiate", &challenge));
+}
+
+TEST(HttpAuthHandlerNegotiateParseTest, ParseLaterRoundChallenge) {
+  // Later rounds should always have a Base64 encoded token.
+  std::string challenge_text = "Negotiate Zm9vYmFy";
+  HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+                                       challenge_text.end());
+  std::string encoded_token;
+  std::string decoded_token;
+  EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+            ParseLaterRoundChallenge("negotiate", &challenge, &encoded_token,
+                                     &decoded_token));
+  EXPECT_EQ("Zm9vYmFy", encoded_token);
+  EXPECT_EQ("foobar", decoded_token);
+}
+
+TEST(HttpAuthHandlerNegotiateParseTest,
+     ParseAnotherNegotiateChallenge_MissingToken) {
+  std::string challenge_text = "Negotiate";
+  HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+                                       challenge_text.end());
+  std::string encoded_token;
+  std::string decoded_token;
+  EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+            ParseLaterRoundChallenge("negotiate", &challenge, &encoded_token,
+                                     &decoded_token));
+}
+
+TEST(HttpAuthHandlerNegotiateParseTest,
+     ParseAnotherNegotiateChallenge_InvalidToken) {
+  std::string challenge_text = "Negotiate ***";
+  HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+                                       challenge_text.end());
+  std::string encoded_token;
+  std::string decoded_token;
+  EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+            ParseLaterRoundChallenge("negotiate", &challenge, &encoded_token,
+                                     &decoded_token));
+}
+}
diff --git a/net/http/http_auth_sspi_win.cc b/net/http/http_auth_sspi_win.cc
index c935d33..f59ce4d 100644
--- a/net/http/http_auth_sspi_win.cc
+++ b/net/http/http_auth_sspi_win.cc
@@ -13,7 +13,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_auth.h"
-#include "net/http/http_auth_challenge_tokenizer.h"
+#include "net/http/http_auth_multi_round_parse.h"
 
 namespace net {
 
@@ -282,37 +282,18 @@
 
 HttpAuth::AuthorizationResult HttpAuthSSPI::ParseChallenge(
     HttpAuthChallengeTokenizer* tok) {
-  // Verify the challenge's auth-scheme.
-  if (!base::LowerCaseEqualsASCII(tok->scheme(),
-                                  base::StringToLowerASCII(scheme_).c_str()))
-    return HttpAuth::AUTHORIZATION_RESULT_INVALID;
-
-  std::string encoded_auth_token = tok->base64_param();
-  if (encoded_auth_token.empty()) {
-    // If a context has already been established, an empty challenge
-    // should be treated as a rejection of the current attempt.
-    if (SecIsValidHandle(&ctxt_))
-      return HttpAuth::AUTHORIZATION_RESULT_REJECT;
-    DCHECK(decoded_server_auth_token_.empty());
-    return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
-  } else {
-    // If a context has not already been established, additional tokens should
-    // not be present in the auth challenge.
-    if (!SecIsValidHandle(&ctxt_))
-      return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+  if (!SecIsValidHandle(&ctxt_)) {
+    return net::ParseFirstRoundChallenge(scheme_, tok);
   }
-
-  std::string decoded_auth_token;
-  bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
-  if (!base64_rv)
-    return HttpAuth::AUTHORIZATION_RESULT_INVALID;
-  decoded_server_auth_token_ = decoded_auth_token;
-  return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+  std::string encoded_auth_token;
+  return net::ParseLaterRoundChallenge(scheme_, tok, &encoded_auth_token,
+                                       &decoded_server_auth_token_);
 }
 
 int HttpAuthSSPI::GenerateAuthToken(const AuthCredentials* credentials,
                                     const std::string& spn,
-                                    std::string* auth_token) {
+                                    std::string* auth_token,
+                                    const CompletionCallback& /*callback*/) {
   // Initial challenge.
   if (!SecIsValidHandle(&cred_)) {
     int rv = OnFirstRound(credentials);
diff --git a/net/http/http_auth_sspi_win.h b/net/http/http_auth_sspi_win.h
index befc1bf1..1d524fa1 100644
--- a/net/http/http_auth_sspi_win.h
+++ b/net/http/http_auth_sspi_win.h
@@ -17,6 +17,7 @@
 #include <string>
 
 #include "base/strings/string16.h"
+#include "net/base/completion_callback.h"
 #include "net/base/net_export.h"
 #include "net/http/http_auth.h"
 
@@ -120,19 +121,36 @@
   HttpAuth::AuthorizationResult ParseChallenge(
       HttpAuthChallengeTokenizer* tok);
 
-  // Generates an authentication token for the service specified by the
-  // Service Principal Name |spn| and stores the value in |*auth_token|.
-  // If the return value is not |OK|, then the value of |*auth_token| is
-  // unspecified. ERR_IO_PENDING is not a valid return code.
+  // Generates an authentication token.
+  //
+  // The return value is an error code. The authentication token will be
+  // returned in |*auth_token|. If the result code is not |OK|, the value of
+  // |*auth_token| is unspecified.
+  //
+  // If the operation cannot be completed synchronously, |ERR_IO_PENDING| will
+  // be returned and the real result code will be passed to the completion
+  // callback.  Otherwise the result code is returned immediately from this
+  // call.
+  //
+  // If the HttpAuthSPPI object is deleted before completion then the callback
+  // will not be called.
+  //
+  // If no immediate result is returned then |auth_token| must remain valid
+  // until the callback has been called.
+  //
+  // |spn| is the Service Principal Name of the server that the token is
+  // being generated for.
+  //
   // If this is the first round of a multiple round scheme, credentials are
-  // obtained using |*credentials|. If |credentials| is NULL, the credentials
-  // for the currently logged in user are used instead.
+  // obtained using |*credentials|. If |credentials| is NULL, the default
+  // credentials are used instead.
   int GenerateAuthToken(const AuthCredentials* credentials,
                         const std::string& spn,
-                        std::string* auth_token);
+                        std::string* auth_token,
+                        const CompletionCallback& callback);
 
   // Delegation is allowed on the Kerberos ticket. This allows certain servers
-  // to act as the user, such as an IIS server retrieiving data from a
+  // to act as the user, such as an IIS server retrieving data from a
   // Kerberized MSSQL server.
   void Delegate();
 
diff --git a/net/http/http_auth_sspi_win_unittest.cc b/net/http/http_auth_sspi_win_unittest.cc
index 586822d2..feebaf0c 100644
--- a/net/http/http_auth_sspi_win_unittest.cc
+++ b/net/http/http_auth_sspi_win_unittest.cc
@@ -25,6 +25,12 @@
 
 const ULONG kMaxTokenLength = 100;
 
+void UnexpectedCallback(int result) {
+  // At present getting tokens from gssapi is fully synchronous, so the callback
+  // should never be called.
+  ADD_FAILURE();
+}
+
 }  // namespace
 
 TEST(HttpAuthSSPITest, SplitUserAndDomain) {
@@ -84,7 +90,8 @@
   // Generate an auth token and create another thing.
   std::string auth_token;
   EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(NULL, "HTTP/intranet.google.com",
-                                            &auth_token));
+                                            &auth_token,
+                                            base::Bind(&UnexpectedCallback)));
 
   std::string second_challenge_text = "Negotiate Zm9vYmFy";
   HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
@@ -120,7 +127,8 @@
 
   std::string auth_token;
   EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(NULL, "HTTP/intranet.google.com",
-                                            &auth_token));
+                                            &auth_token,
+                                            base::Bind(&UnexpectedCallback)));
   std::string second_challenge_text = "Negotiate";
   HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
                                               second_challenge_text.end());
@@ -142,7 +150,8 @@
 
   std::string auth_token;
   EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(NULL, "HTTP/intranet.google.com",
-                                            &auth_token));
+                                            &auth_token,
+                                            base::Bind(&UnexpectedCallback)));
   std::string second_challenge_text = "Negotiate =happyjoy=";
   HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
                                               second_challenge_text.end());
diff --git a/net/http/http_auth_unittest.cc b/net/http/http_auth_unittest.cc
index 52e8294..a81a409 100644
--- a/net/http/http_auth_unittest.cc
+++ b/net/http/http_auth_unittest.cc
@@ -69,57 +69,56 @@
     HttpAuth::Scheme challenge_scheme;
     const char* challenge_realm;
   } tests[] = {
-    {
-      // Basic is the only challenge type, pick it.
-      "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
-      "www-authenticate: Basic realm=\"BasicRealm\"\n",
+      {
+       // Basic is the only challenge type, pick it.
+       "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
+       "www-authenticate: Basic realm=\"BasicRealm\"\n",
 
-      HttpAuth::AUTH_SCHEME_BASIC,
-      "BasicRealm",
-    },
-    {
-      // Fake is the only challenge type, but it is unsupported.
-      "Y: Digest realm=\"FooBar\", nonce=\"aaaaaaaaaa\"\n"
-      "www-authenticate: Fake realm=\"FooBar\"\n",
+       HttpAuth::AUTH_SCHEME_BASIC,
+       "BasicRealm",
+      },
+      {
+       // Fake is the only challenge type, but it is unsupported.
+       "Y: Digest realm=\"FooBar\", nonce=\"aaaaaaaaaa\"\n"
+       "www-authenticate: Fake realm=\"FooBar\"\n",
 
-      HttpAuth::AUTH_SCHEME_MAX,
-      "",
-    },
-    {
-      // Pick Digest over Basic.
-      "www-authenticate: Basic realm=\"FooBar\"\n"
-      "www-authenticate: Fake realm=\"FooBar\"\n"
-      "www-authenticate: nonce=\"aaaaaaaaaa\"\n"
-      "www-authenticate: Digest realm=\"DigestRealm\", nonce=\"aaaaaaaaaa\"\n",
+       HttpAuth::AUTH_SCHEME_MAX,
+       "",
+      },
+      {
+       // Pick Digest over Basic.
+       "www-authenticate: Basic realm=\"FooBar\"\n"
+       "www-authenticate: Fake realm=\"FooBar\"\n"
+       "www-authenticate: nonce=\"aaaaaaaaaa\"\n"
+       "www-authenticate: Digest realm=\"DigestRealm\", nonce=\"aaaaaaaaaa\"\n",
 
-      HttpAuth::AUTH_SCHEME_DIGEST,
-      "DigestRealm",
-    },
-    {
-      // Handle an empty header correctly.
-      "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
-      "www-authenticate:\n",
+       HttpAuth::AUTH_SCHEME_DIGEST,
+       "DigestRealm",
+      },
+      {
+       // Handle an empty header correctly.
+       "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
+       "www-authenticate:\n",
 
-      HttpAuth::AUTH_SCHEME_MAX,
-      "",
-    },
-    {
-      "WWW-Authenticate: Negotiate\n"
-      "WWW-Authenticate: NTLM\n",
+       HttpAuth::AUTH_SCHEME_MAX,
+       "",
+      },
+      {
+       "WWW-Authenticate: Negotiate\n"
+       "WWW-Authenticate: NTLM\n",
 
-#if defined(USE_KERBEROS)
-      // Choose Negotiate over NTLM on all platforms.
-      // TODO(ahendrickson): This may be flaky on Linux and OSX as it
-      // relies on being able to load one of the known .so files
-      // for gssapi.
-      HttpAuth::AUTH_SCHEME_NEGOTIATE,
+#if defined(USE_KERBEROS) && !defined(OS_ANDROID)
+       // Choose Negotiate over NTLM on all platforms.
+       // TODO(ahendrickson): This may be flaky on Linux and OSX as it
+       // relies on being able to load one of the known .so files
+       // for gssapi.
+       HttpAuth::AUTH_SCHEME_NEGOTIATE,
 #else
-      // On systems that don't use Kerberos fall back to NTLM.
-      HttpAuth::AUTH_SCHEME_NTLM,
+       // On systems that don't use Kerberos fall back to NTLM.
+       HttpAuth::AUTH_SCHEME_NTLM,
 #endif  // defined(USE_KERBEROS)
-      "",
-    }
-  };
+       "",
+      }};
   GURL origin("http://www.example.com");
   std::set<HttpAuth::Scheme> disabled_schemes;
   MockAllowURLSecurityManager url_security_manager;
diff --git a/net/net.gyp b/net/net.gyp
index b231499..a997712 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -10,11 +10,11 @@
     'net_test_extra_libs': [],
     'linux_link_kerberos%': 0,
     'conditions': [
-      ['chromeos==1 or embedded==1 or OS=="android" or OS=="ios"', {
-        # Disable Kerberos on ChromeOS, Android and iOS, at least for now.
+      ['chromeos==1 or embedded==1 or OS=="ios"', {
+        # Disable Kerberos on ChromeOS and iOS, at least for now.
         # It needs configuration (krb5.conf and so on).
         'use_kerberos%': 0,
-      }, {  # chromeos == 0 and embedded==0 and OS!="android" and OS!="ios"
+      }, {  # chromeos == 0 and embedded==0 and OS!="ios"
         'use_kerberos%': 1,
       }],
       ['OS=="android" and target_arch != "ia32"', {
@@ -222,14 +222,21 @@
           'defines': [
             'USE_KERBEROS',
           ],
-        }, { # use_kerberos == 0
+        }],
+        [ 'use_kerberos==0 or OS == "android"', {
+          # These are excluded on Android, because the actual Kerberos support,
+          # which these test, is in a separate app on Android.
           'sources!': [
             'http/http_auth_gssapi_posix_unittest.cc',
-            'http/http_auth_handler_negotiate_unittest.cc',
             'http/mock_gssapi_library_posix.cc',
             'http/mock_gssapi_library_posix.h',
           ],
         }],
+       [ 'use_kerberos==0', {
+          'sources!': [
+            'http/http_auth_handler_negotiate_unittest.cc',
+          ],
+        }],
         [ 'use_openssl == 1 or (desktop_linux == 0 and chromeos == 0 and OS != "ios")', {
           # Only include this test when on Posix and using NSS for
           # cert verification or on iOS (which also uses NSS for certs).
@@ -1355,6 +1362,7 @@
             'android/java/src/org/chromium/net/AndroidNetworkLibrary.java',
             'android/java/src/org/chromium/net/AndroidPrivateKey.java',
             'android/java/src/org/chromium/net/GURLUtils.java',
+            'android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java',
             'android/java/src/org/chromium/net/NetStringUtil.java',
             'android/java/src/org/chromium/net/NetworkChangeNotifier.java',
             'android/java/src/org/chromium/net/ProxyChangeListener.java',
@@ -1371,6 +1379,7 @@
           'sources': [
             'android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java',
             'test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java',
+            'test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java',
           ],
           'variables': {
             'jni_gen_package': 'net/test',
@@ -1418,6 +1427,7 @@
             'net_test_support',
             'url_request_failed_job_java',
             '../base/base.gyp:base_java',
+            'net_java',
             '<@(net_test_extra_libs)',
           ],
           'includes': [ '../build/java.gypi' ],
@@ -1493,6 +1503,7 @@
           'dependencies': [
             'net_java',
             'net_javatests',
+            'net_java_test_support',
             'net_unittests',
           ],
           'conditions': [
@@ -1514,6 +1525,8 @@
           'variables': {
             'test_suite_name': 'net_unittests',
             'isolate_file': 'net_unittests.isolate',
+            'android_manifest_path': 'android/unittest_support/AndroidManifest.xml',
+            'resource_dir': 'android/unittest_support/res',
             'conditions': [
               ['v8_use_external_startup_data==1', {
                 'asset_location': '<(PRODUCT_DIR)/net_unittests_apk/assets',
@@ -1530,6 +1543,26 @@
           },
           'includes': [ '../build/apk_test.gypi' ],
         },
+        {
+          'target_name': 'net_junit_tests',
+          'type': 'none',
+          'dependencies': [
+            'net_java',
+            '../base/base.gyp:base',
+            '../base/base.gyp:base_java_test_support',
+            '../testing/android/junit/junit_test.gyp:junit_test_support',
+          ],
+          'variables': {
+            'main_class': 'org.chromium.testing.local.JunitTestMain',
+            'src_paths': [
+              'android/junit/',
+            ],
+          },
+          'includes': [
+            '../build/host_jar.gypi',
+          ],
+        },
+  
       ],
     }],
     ['OS == "android" or OS == "linux"', {
diff --git a/net/net.gypi b/net/net.gypi
index 19f5e90..630be11 100644
--- a/net/net.gypi
+++ b/net/net.gypi
@@ -184,6 +184,8 @@
       'android/cert_verify_result_android.h',
       'android/gurl_utils.cc',
       'android/gurl_utils.h',
+      'android/http_auth_negotiate_android.cc',
+      'android/http_auth_negotiate_android.h',
       'android/keystore.cc',
       'android/keystore.h',
       'android/keystore_openssl.cc',
@@ -657,6 +659,8 @@
       'http/http_auth_handler_ntlm.h',
       'http/http_auth_handler_ntlm_portable.cc',
       'http/http_auth_handler_ntlm_win.cc',
+      'http/http_auth_multi_round_parse.cc',
+      'http/http_auth_multi_round_parse.h',
       'http/http_auth_sspi_win.cc',
       'http/http_auth_sspi_win.h',
       'http/http_basic_state.cc',
@@ -1289,6 +1293,9 @@
       'extras/sqlite/sqlite_persistent_cookie_store.h',
     ],
     'net_test_sources': [
+      'android/dummy_spnego_authenticator.cc',
+      'android/dummy_spnego_authenticator.h',
+      'android/http_auth_negotiate_android_unittest.cc',
       'android/keystore_unittest.cc',
       'android/network_change_notifier_android_unittest.cc',
       'base/address_list_unittest.cc',
@@ -1439,6 +1446,7 @@
       'http/http_auth_handler_mock.h',
       'http/http_auth_handler_negotiate_unittest.cc',
       'http/http_auth_handler_unittest.cc',
+      'http/http_auth_multi_round_parse_unittest.cc',
       'http/http_auth_sspi_win_unittest.cc',
       'http/http_auth_unittest.cc',
       'http/http_basic_state_unittest.cc',
diff --git a/net/net_common.gypi b/net/net_common.gypi
index 7a888a8a..18d4f03 100644
--- a/net/net_common.gypi
+++ b/net/net_common.gypi
@@ -390,6 +390,8 @@
           'cert/cert_database_openssl.cc',
           'cert/cert_verify_proc_openssl.cc',
           'cert/test_root_certs_openssl.cc',
+          'http/http_auth_gssapi_posix.cc',
+          'http/http_auth_gssapi_posix.h',
         ],
       },
     ],
diff --git a/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java b/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java
new file mode 100644
index 0000000..1c8b0c0
--- /dev/null
+++ b/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java
@@ -0,0 +1,186 @@
+// Copyright 2015 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.net.test;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.NetworkErrorException;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.NativeClassQualifiedName;
+import org.chromium.net.HttpNegotiateConstants;
+
+import java.io.IOException;
+
+/**
+ * Dummy Android authenticator, to test SPNEGO/Keberos support on Android. This is deliberately
+ * minimal, and is not intended as an example of how to write a real SPNEGO Authenticator.
+ */
+@JNINamespace("net::android")
+public class DummySpnegoAuthenticator extends AbstractAccountAuthenticator {
+    private static final String ACCOUNT_TYPE = "org.chromium.test.DummySpnegoAuthenticator";
+    private static final String ACCOUNT_NAME = "DummySpnegoAccount";
+    private static int sResult;
+    private static String sToken;
+    private static boolean sCheckArguments;
+    private static long sNativeDummySpnegoAuthenticator;
+    private static final int GSS_S_COMPLETE = 0;
+    private static final int GSS_S_CONTINUE_NEEDED = 1;
+    private static final int GSS_S_FAILURE = 2;
+
+    /**
+     * @param context
+     */
+    public DummySpnegoAuthenticator(Context context) {
+        super(context);
+    }
+
+    @Override
+    public Bundle addAccount(AccountAuthenticatorResponse arg0, String accountType, String arg2,
+            String[] arg3, Bundle arg4) throws NetworkErrorException {
+        Bundle result = new Bundle();
+        result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_REQUEST);
+        result.putString(AccountManager.KEY_ERROR_MESSAGE, "Can't add new SPNEGO accounts");
+        return result;
+    }
+
+    @Override
+    public Bundle confirmCredentials(AccountAuthenticatorResponse arg0, Account arg1, Bundle arg2)
+            throws NetworkErrorException {
+        Bundle result = new Bundle();
+        result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+        return result;
+    }
+
+    @Override
+    public Bundle editProperties(AccountAuthenticatorResponse arg0, String arg1) {
+        return new Bundle();
+    }
+
+    @Override
+    public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
+            String authTokenType, Bundle options) throws NetworkErrorException {
+        long nativeQuery = nativeGetNextQuery(sNativeDummySpnegoAuthenticator);
+        String incomingToken = options.getString(HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN);
+        nativeCheckGetTokenArguments(nativeQuery, incomingToken);
+        Bundle result = new Bundle();
+        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+        result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+        result.putString(AccountManager.KEY_AUTHTOKEN, nativeGetTokenToReturn(nativeQuery));
+        result.putInt(HttpNegotiateConstants.KEY_SPNEGO_RESULT,
+                decodeResult(nativeGetResult(nativeQuery)));
+        return result;
+    }
+
+    /**
+     * @param nativeGetResult
+     * @return
+     */
+    private int decodeResult(int gssApiResult) {
+        // This only handles the result values currently used in the tests.
+        switch (gssApiResult) {
+            case GSS_S_COMPLETE:
+            case GSS_S_CONTINUE_NEEDED:
+                return 0;
+            case GSS_S_FAILURE:
+                return HttpNegotiateConstants.ERR_MISSING_AUTH_CREDENTIALS;
+            default:
+                return HttpNegotiateConstants.ERR_UNEXPECTED;
+        }
+    }
+
+    @Override
+    public String getAuthTokenLabel(String arg0) {
+        return "Spnego " + arg0;
+    }
+
+    @Override
+    public Bundle hasFeatures(AccountAuthenticatorResponse arg0, Account arg1, String[] features)
+            throws NetworkErrorException {
+        Bundle result = new Bundle();
+        for (String feature : features) {
+            if (!feature.equals(HttpNegotiateConstants.SPNEGO_FEATURE)) {
+                result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+                return result;
+            }
+        }
+        result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+        return result;
+    }
+
+    @Override
+    public Bundle updateCredentials(AccountAuthenticatorResponse arg0, Account arg1, String arg2,
+            Bundle arg3) throws NetworkErrorException {
+        Bundle result = new Bundle();
+        result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_REQUEST);
+        result.putString(AccountManager.KEY_ERROR_MESSAGE, "Can't add new SPNEGO accounts");
+        return result;
+    }
+
+    /**
+     * Called from tests, sets up the test account, if it doesn't already exist
+     */
+    @CalledByNative
+    private static void ensureTestAccountExists() {
+        Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
+        AccountManager am = AccountManager.get(activity);
+        Account account = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
+        am.addAccountExplicitly(account, null, null);
+    }
+
+    /**
+     * Called from tests to tidy up test accounts.
+     */
+    @SuppressWarnings("deprecation")
+    @CalledByNative
+    private static void removeTestAccounts() {
+        Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
+        AccountManager am = AccountManager.get(activity);
+        String features[] = {HttpNegotiateConstants.SPNEGO_FEATURE};
+        try {
+            Account accounts[] =
+                    am.getAccountsByTypeAndFeatures(ACCOUNT_TYPE, features, null, null).getResult();
+            for (Account account : accounts) {
+                // Deprecated, but the replacement not available on Android JB.
+                am.removeAccount(account, null, null).getResult();
+            }
+        } catch (OperationCanceledException | AuthenticatorException | IOException e) {
+            // Should never happen. This is tidy-up after the tests. Ignore.
+        }
+    }
+
+    @CalledByNative
+    private static void setNativeAuthenticator(long nativeDummySpnegoAuthenticator) {
+        sNativeDummySpnegoAuthenticator = nativeDummySpnegoAuthenticator;
+    }
+
+    /**
+     * Send the relevant decoded arguments of getAuthToken to C++ for checking by googletest checks
+     * If the checks fail then the C++ unit test using this authenticator will fail.
+     *
+     * @param authTokenType
+     * @param spn
+     * @param incomingToken
+     */
+    @NativeClassQualifiedName("DummySpnegoAuthenticator::SecurityContextQuery")
+    private native void nativeCheckGetTokenArguments(long nativeQuery, String incomingToken);
+
+    @NativeClassQualifiedName("DummySpnegoAuthenticator::SecurityContextQuery")
+    private native String nativeGetTokenToReturn(long nativeQuery);
+
+    @NativeClassQualifiedName("DummySpnegoAuthenticator::SecurityContextQuery")
+    private native int nativeGetResult(long nativeQuery);
+
+    private native long nativeGetNextQuery(long nativeDummySpnegoAuthenticator);
+}
diff --git a/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticatorService.java b/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticatorService.java
new file mode 100644
index 0000000..cc732a6
--- /dev/null
+++ b/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticatorService.java
@@ -0,0 +1,22 @@
+// Copyright 2015 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.net.test;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Authenticator service for testing SPNEGO (Kerberos) support.
+ */
+public class DummySpnegoAuthenticatorService extends Service {
+    private static DummySpnegoAuthenticator sAuthenticator = null;
+
+    @Override
+    public IBinder onBind(Intent arg0) {
+        if (sAuthenticator == null) sAuthenticator = new DummySpnegoAuthenticator(this);
+        return sAuthenticator.getIBinder();
+    }
+}
diff --git a/net/test/run_all_unittests.cc b/net/test/run_all_unittests.cc
index 25016ca..7da7991 100644
--- a/net/test/run_all_unittests.cc
+++ b/net/test/run_all_unittests.cc
@@ -15,6 +15,8 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_registrar.h"
 #include "base/test/test_file_util.h"
+#include "base/test/test_ui_thread_android.h"
+#include "net/android/dummy_spnego_authenticator.h"
 #include "net/android/net_jni_registrar.h"
 #include "url/android/url_jni_registrar.h"
 #endif
@@ -32,9 +34,12 @@
 
 #if defined(OS_ANDROID)
   const base::android::RegistrationMethod kNetTestRegisteredMethods[] = {
-    {"NetAndroid", net::android::RegisterJni},
-    {"TestFileUtil", base::RegisterContentUriTestUtils},
-    {"UrlAndroid", url::android::RegisterJni},
+      {"DummySpnegoAuthenticator",
+       net::android::DummySpnegoAuthenticator::RegisterJni},
+      {"NetAndroid", net::android::RegisterJni},
+      {"TestFileUtil", base::RegisterContentUriTestUtils},
+      {"TestUiThreadAndroid", base::RegisterTestUiThreadAndroid},
+      {"UrlAndroid", url::android::RegisterJni},
   };
 
   // Register JNI bindings for android. Doing it early as the test suite setup