First step in enabling creating tabs without an Activity on Android.

This patch modifies the ContentBrowserClient::OpenURL method to pass a
callback, to be invoked when the content::WebContents* is available,
rather than returning it immediately as this will be an asynchronous
process on Android.

For actually opening a new tab, we create a ServiceTabCreator class which
has the ability to create tabs from Services. This method will fire
an intent to start the activity asynchronously.

The follow-up patch will make the activity communicate back a reference
to the created TabAndroid / WebContents, which allows the Service to
get it, and finalize the callback as expected.

BUG=454809

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

Cr-Commit-Position: refs/heads/master@{#315855}
diff --git a/chrome/browser/android/chrome_jni_registrar.cc b/chrome/browser/android/chrome_jni_registrar.cc
index 32961e7..88eb4f0 100644
--- a/chrome/browser/android/chrome_jni_registrar.cc
+++ b/chrome/browser/android/chrome_jni_registrar.cc
@@ -44,6 +44,7 @@
 #include "chrome/browser/android/profiles/profile_downloader_android.h"
 #include "chrome/browser/android/provider/chrome_browser_provider.h"
 #include "chrome/browser/android/recently_closed_tabs_bridge.h"
+#include "chrome/browser/android/service_tab_launcher.h"
 #include "chrome/browser/android/shortcut_helper.h"
 #include "chrome/browser/android/signin/account_management_screen_helper.h"
 #include "chrome/browser/android/signin/signin_manager_android.h"
@@ -209,6 +210,7 @@
     {"ProfileSyncService", ProfileSyncServiceAndroid::Register},
     {"RecentlyClosedBridge", RecentlyClosedTabsBridge::Register},
     {"SceneLayer", chrome::android::RegisterSceneLayer},
+    {"ServiceTabLauncher", ServiceTabLauncher::RegisterServiceTabLauncher},
     {"SigninManager", SigninManagerAndroid::Register},
     {"SqliteCursor", SQLiteCursor::RegisterSqliteCursor},
     {"SSLClientCertificateRequest", RegisterSSLClientCertificateRequestAndroid},
diff --git a/chrome/browser/android/service_tab_launcher.cc b/chrome/browser/android/service_tab_launcher.cc
new file mode 100644
index 0000000..7faab0d
--- /dev/null
+++ b/chrome/browser/android/service_tab_launcher.cc
@@ -0,0 +1,60 @@
+// 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 "chrome/browser/android/service_tab_launcher.h"
+
+#include "base/android/jni_string.h"
+#include "base/callback.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/page_navigator.h"
+#include "jni/ServiceTabLauncher_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::GetApplicationContext;
+
+namespace chrome {
+namespace android {
+
+// static
+ServiceTabLauncher* ServiceTabLauncher::GetInstance() {
+  return Singleton<ServiceTabLauncher>::get();
+}
+
+ServiceTabLauncher::ServiceTabLauncher() {
+  java_object_.Reset(
+      Java_ServiceTabLauncher_create(
+          AttachCurrentThread(),
+          reinterpret_cast<intptr_t>(this),
+          GetApplicationContext()));
+}
+
+ServiceTabLauncher::~ServiceTabLauncher() {}
+
+void ServiceTabLauncher::LaunchTab(
+    content::BrowserContext* browser_context,
+    const content::OpenURLParams& params,
+    const base::Callback<void(content::WebContents*)>& callback) {
+  JNIEnv* env = AttachCurrentThread();
+  ScopedJavaLocalRef<jstring> url = ConvertUTF8ToJavaString(
+      env, params.url.spec());
+
+  Java_ServiceTabLauncher_launchTab(env,
+                                    java_object_.obj(),
+                                    url.obj(),
+                                    params.disposition,
+                                    browser_context->IsOffTheRecord());
+
+  // TODO(peter): We need to wait for the Android Activity to reply to the
+  // launch intent with the ID of the launched Web Contents, so that the Java
+  // side can invoke a method on the native side with the request id and the
+  // WebContents enabling us to invoke |callback|. See https://crbug.com/454809.
+}
+
+bool ServiceTabLauncher::RegisterServiceTabLauncher(JNIEnv* env) {
+  return RegisterNativesImpl(env);
+}
+
+}  // namespace android
+}  // namespace chrome
diff --git a/chrome/browser/android/service_tab_launcher.h b/chrome/browser/android/service_tab_launcher.h
new file mode 100644
index 0000000..3e9fb233
--- /dev/null
+++ b/chrome/browser/android/service_tab_launcher.h
@@ -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.
+
+#ifndef CHROME_BROWSER_ANDROID_SERVICE_TAB_LAUNCHER_H_
+#define CHROME_BROWSER_ANDROID_SERVICE_TAB_LAUNCHER_H_
+
+#include "base/android/jni_android.h"
+#include "base/callback_forward.h"
+#include "base/memory/singleton.h"
+
+namespace content {
+class BrowserContext;
+struct OpenURLParams;
+class WebContents;
+}
+
+namespace chrome {
+namespace android {
+
+// Launcher for creating new tabs on Android from a background service, where
+// there may not necessarily be an Activity or a tab model at all.
+class ServiceTabLauncher {
+ public:
+  // Returns the singleton instance of the service tab launcher.
+  static ServiceTabLauncher* GetInstance();
+
+  // Launches a new tab when we're in a Service rather than in an Activity.
+  // |callback| will be invoked with the resulting content::WebContents* when
+  // the tab is avialable. This method must only be called from the UI thread.
+  void LaunchTab(content::BrowserContext* browser_context,
+                 const content::OpenURLParams& params,
+                 const base::Callback<void(content::WebContents*)>& callback);
+
+  static bool RegisterServiceTabLauncher(JNIEnv* env);
+
+ private:
+  friend struct DefaultSingletonTraits<ServiceTabLauncher>;
+
+  ServiceTabLauncher();
+  ~ServiceTabLauncher();
+
+  base::android::ScopedJavaGlobalRef<jobject> java_object_;
+
+  DISALLOW_COPY_AND_ASSIGN(ServiceTabLauncher);
+};
+
+}  // namespace android
+}  // namespace chrome
+
+#endif  // CHROME_BROWSER_ANDROID_SERVICE_TAB_LAUNCHER_H_
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index dc10e10a..a0ad854 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -156,6 +156,7 @@
 #include "chrome/browser/chrome_browser_main_linux.h"
 #elif defined(OS_ANDROID)
 #include "chrome/browser/android/new_tab_page_url_handler.h"
+#include "chrome/browser/android/service_tab_launcher.h"
 #include "chrome/browser/android/webapps/single_tab_mode_tab_helper.h"
 #include "chrome/browser/chrome_browser_main_android.h"
 #include "chrome/common/descriptors_android.h"
@@ -2522,9 +2523,10 @@
           browser_context, security_origin, type);
 }
 
-content::WebContents* ChromeContentBrowserClient::OpenURL(
+void ChromeContentBrowserClient::OpenURL(
     content::BrowserContext* browser_context,
-    const content::OpenURLParams& params) {
+    const content::OpenURLParams& params,
+    const base::Callback<void(content::WebContents*)>& callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
 #if !defined(OS_ANDROID) && !defined(OS_IOS)
@@ -2535,12 +2537,13 @@
   nav_params.user_gesture = params.user_gesture;
 
   Navigate(&nav_params);
-  return nav_params.target_contents;
+  callback.Run(nav_params.target_contents);
+#elif defined(OS_ANDROID)
+  chrome::android::ServiceTabLauncher::GetInstance()->LaunchTab(
+      browser_context, params, callback);
 #else
-  // TODO(mlamouri): write a chrome::Navigate() method for Android and iOS.
-  // See https://crbug.com/448409.
-  return nullptr;
-#endif // !defined(OS_ANDROID) && !defined(OS_IOS)
+  NOTIMPLEMENTED();
+#endif
 }
 
 content::DevToolsManagerDelegate*
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 40e700f..a456f7b3 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -281,8 +281,10 @@
                                   const GURL& security_origin,
                                   content::MediaStreamType type) override;
 
-  content::WebContents* OpenURL(content::BrowserContext* browser_context,
-                                const content::OpenURLParams& params) override;
+  void OpenURL(content::BrowserContext* browser_context,
+               const content::OpenURLParams& params,
+               const base::Callback<void(content::WebContents*)>& callback)
+      override;
 
  private:
   friend class DisableWebRtcEncryptionFlagTest;
diff --git a/chrome/browser/chrome_content_browser_client_unittest.cc b/chrome/browser/chrome_content_browser_client_unittest.cc
index 42c45c5f..c0235f1 100644
--- a/chrome/browser/chrome_content_browser_client_unittest.cc
+++ b/chrome/browser/chrome_content_browser_client_unittest.cc
@@ -32,11 +32,18 @@
   EXPECT_TRUE(client.ShouldAssignSiteForURL(GURL("https://www.google.com")));
 }
 
-using ChromeContentBrowserClientWindowTest = BrowserWithTestWindowTest;
-
 // BrowserWithTestWindowTest doesn't work on iOS and Android.
 #if !defined(OS_ANDROID) && !defined(OS_IOS)
 
+using ChromeContentBrowserClientWindowTest = BrowserWithTestWindowTest;
+
+static void DidOpenURLForWindowTest(content::WebContents** target_contents,
+                                    content::WebContents* opened_contents) {
+  DCHECK(target_contents);
+
+  *target_contents = opened_contents;
+}
+
 // This test opens two URLs using ContentBrowserClient::OpenURL. It expects the
 // URLs to be opened in new tabs and activated, changing the active tabs after
 // each call and increasing the tab count by 2.
@@ -54,13 +61,20 @@
                                   NEW_FOREGROUND_TAB,
                                   ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
                                   false);
-    content::WebContents* contents =
-        client.OpenURL(browser()->profile(), params);
-    EXPECT_TRUE(contents);
+    // TODO(peter): We should have more in-depth browser tests for the window
+    // opening functionality, which also covers Android. This test can currently
+    // only be ran on platforms where OpenURL is implemented synchronously.
+    // See https://crbug.com/457667.
+    content::WebContents* web_contents = nullptr;
+    client.OpenURL(browser()->profile(),
+                   params,
+                   base::Bind(&DidOpenURLForWindowTest, &web_contents));
+
+    EXPECT_TRUE(web_contents);
 
     content::WebContents* active_contents = browser()->tab_strip_model()->
         GetActiveWebContents();
-    EXPECT_EQ(contents, active_contents);
+    EXPECT_EQ(web_contents, active_contents);
     EXPECT_EQ(url, active_contents->GetVisibleURL());
   }