Expose the Site Engagement Service to Java.

This CL adds a Java-side interface to the Site Engagement Service, as
well as a basic integration test for the interface. This allows Java
code (particularly tests) to programatically adjust and retrieve the
score.

BUG=671100

Review-Url: https://codereview.chromium.org/2553013002
Cr-Commit-Position: refs/heads/master@{#437825}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/engagement/SiteEngagementService.java b/chrome/android/java/src/org/chromium/chrome/browser/engagement/SiteEngagementService.java
new file mode 100644
index 0000000..316aa147
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/engagement/SiteEngagementService.java
@@ -0,0 +1,72 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.engagement;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.annotations.CalledByNative;
+
+import org.chromium.chrome.browser.profiles.Profile;
+
+/**
+ * Provides access to the Site Engagement Service for a profile.
+ *
+ * Site engagement measures the level of engagement that a user has with an origin. This class
+ * allows Java to retrieve and modify engagement scores for URLs.
+ */
+public class SiteEngagementService {
+
+    /** Pointer to the native side SiteEngagementServiceAndroid shim. */
+    private long mNativePointer;
+
+    /**
+     * Returns a SiteEngagementService for the provided profile.
+     * Must be called on the UI thread.
+     */
+    public static SiteEngagementService getForProfile(Profile profile) {
+        assert ThreadUtils.runningOnUiThread();
+        return nativeSiteEngagementServiceForProfile(profile);
+    }
+
+    /**
+     * Returns the engagement score for the provided URL.
+     * Must be called on the UI thread.
+     */
+    public double getScore(String url) {
+        assert ThreadUtils.runningOnUiThread();
+        if (mNativePointer == 0) return 0.0;
+        return nativeGetScore(mNativePointer, url);
+    }
+
+    /**
+     * Sets the provided URL to have the provided engagement score.
+     * Must be called on the UI thread.
+     */
+    public void resetScoreForUrl(String url, double score) {
+        assert ThreadUtils.runningOnUiThread();
+        if (mNativePointer == 0) return;
+        nativeResetScoreForURL(mNativePointer, url, score);
+    }
+
+    @CalledByNative
+    private static SiteEngagementService create(long nativePointer) {
+        return new SiteEngagementService(nativePointer);
+    }
+
+    /** This object may only be created via the static getForProfile method. */
+    private SiteEngagementService(long nativePointer) {
+        mNativePointer = nativePointer;
+    }
+
+    @CalledByNative
+    private void onNativeDestroyed() {
+        mNativePointer = 0;
+    }
+
+    private static native SiteEngagementService nativeSiteEngagementServiceForProfile(
+            Profile profile);
+    private native double nativeGetScore(long nativeSiteEngagementServiceAndroid, String url);
+    private native void nativeResetScoreForURL(
+            long nativeSiteEngagementServiceAndroid, String url, double score);
+}
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index bc673e58..6ee404fe 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -341,6 +341,7 @@
   "java/src/org/chromium/chrome/browser/download/ui/SpaceDisplay.java",
   "java/src/org/chromium/chrome/browser/download/ui/ThumbnailProvider.java",
   "java/src/org/chromium/chrome/browser/download/ui/ThumbnailProviderImpl.java",
+  "java/src/org/chromium/chrome/browser/engagement/SiteEngagementService.java",
   "java/src/org/chromium/chrome/browser/externalauth/ExternalAuthUtils.java",
   "java/src/org/chromium/chrome/browser/externalauth/UserRecoverableErrorHandler.java",
   "java/src/org/chromium/chrome/browser/externalauth/VerifiedHandler.java",
@@ -1240,6 +1241,7 @@
   "javatests/src/org/chromium/chrome/browser/download/SystemDownloadNotifierTest.java",
   "javatests/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapterTest.java",
   "javatests/src/org/chromium/chrome/browser/download/ui/StubbedProvider.java",
+  "javatests/src/org/chromium/chrome/browser/engagement/SiteEngagementServiceTest.java",
   "javatests/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImplTest.java",
   "javatests/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandlerTest.java",
   "javatests/src/org/chromium/chrome/browser/externalnav/IntentWithGesturesHandlerTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/engagement/SiteEngagementServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/engagement/SiteEngagementServiceTest.java
new file mode 100644
index 0000000..35e5468
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/engagement/SiteEngagementServiceTest.java
@@ -0,0 +1,65 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.engagement;
+
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.test.ChromeActivityTestCaseBase;
+
+/**
+ * Test for the Site Engagement Service Java binding.
+ */
+public class SiteEngagementServiceTest extends ChromeActivityTestCaseBase<ChromeActivity> {
+
+    public SiteEngagementServiceTest() {
+        super(ChromeActivity.class);
+    }
+
+    /**
+     * Verify that setting the engagement score for a URL and reading it back it works.
+     */
+    @SmallTest
+    @UiThreadTest
+    @Feature({"Engagement"})
+    public void testSettingAndRetrievingScore() {
+        final String url = "https://www.google.com";
+        SiteEngagementService service = SiteEngagementService.getForProfile(
+                getActivity().getActivityTab().getProfile());
+
+        assertEquals(0.0, service.getScore(url));
+        service.resetScoreForUrl(url, 5.0);
+        assertEquals(5.0, service.getScore(url));
+
+        service.resetScoreForUrl(url, 2.0);
+        assertEquals(2.0, service.getScore(url));
+    }
+
+    /**
+     * Verify that repeatedly fetching and throwing away the SiteEngagementService works.
+     */
+    @SmallTest
+    @UiThreadTest
+    @Feature({"Engagement"})
+    public void testRepeatedlyGettingService() {
+        final String url = "https://www.google.com";
+        Profile profile = getActivity().getActivityTab().getProfile();
+
+        assertEquals(0.0, SiteEngagementService.getForProfile(profile).getScore(url));
+        SiteEngagementService.getForProfile(profile).resetScoreForUrl(url, 5.0);
+        assertEquals(5.0, SiteEngagementService.getForProfile(profile).getScore(url));
+
+        SiteEngagementService.getForProfile(profile).resetScoreForUrl(url, 2.0);
+        assertEquals(2.0, SiteEngagementService.getForProfile(profile).getScore(url));
+    }
+
+    @Override
+    public void startMainActivity() throws InterruptedException {
+        startMainActivityOnBlankPage();
+    }
+}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index a166fed8..f59ef26 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3255,6 +3255,8 @@
       "dom_distiller/tab_utils_android.h",
       "download/download_request_infobar_delegate_android.cc",
       "download/download_request_infobar_delegate_android.h",
+      "engagement/site_engagement_service_android.cc",
+      "engagement/site_engagement_service_android.h",
       "geolocation/geolocation_infobar_delegate_android.cc",
       "geolocation/geolocation_infobar_delegate_android.h",
       "history/android/android_history_provider_service.cc",
@@ -3907,6 +3909,7 @@
       "../android/java/src/org/chromium/chrome/browser/download/DownloadItem.java",
       "../android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java",
       "../android/java/src/org/chromium/chrome/browser/download/ui/ThumbnailProviderImpl.java",
+      "../android/java/src/org/chromium/chrome/browser/engagement/SiteEngagementService.java",
       "../android/java/src/org/chromium/chrome/browser/favicon/FaviconHelper.java",
       "../android/java/src/org/chromium/chrome/browser/favicon/LargeIconBridge.java",
       "../android/java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java",
diff --git a/chrome/browser/android/chrome_jni_registrar.cc b/chrome/browser/android/chrome_jni_registrar.cc
index 595bde4..c6abcc156 100644
--- a/chrome/browser/android/chrome_jni_registrar.cc
+++ b/chrome/browser/android/chrome_jni_registrar.cc
@@ -109,6 +109,7 @@
 #include "chrome/browser/autofill/android/personal_data_manager_android.h"
 #include "chrome/browser/dom_distiller/dom_distiller_service_factory_android.h"
 #include "chrome/browser/dom_distiller/tab_utils_android.h"
+#include "chrome/browser/engagement/site_engagement_service_android.h"
 #include "chrome/browser/history/android/sqlite_cursor.h"
 #include "chrome/browser/invalidation/invalidation_service_factory_android.h"
 #include "chrome/browser/media/android/cdm/media_drm_credential_manager.h"
@@ -373,6 +374,7 @@
     {"SigninInvestigator", SigninInvestigatorAndroid::Register},
     {"SigninManager", SigninManagerAndroid::Register},
     {"SingleTabModel", RegisterSingleTabModel},
+    {"SiteEngagementService", SiteEngagementServiceAndroid::Register},
 #if BUILDFLAG(ENABLE_SPELLCHECK)
     {"SpellCheckerSessionBridge", spellcheck::android::RegisterSpellcheckJni},
 #endif
diff --git a/chrome/browser/engagement/site_engagement_service.cc b/chrome/browser/engagement/site_engagement_service.cc
index 66a2b49..3fa29c7 100644
--- a/chrome/browser/engagement/site_engagement_service.cc
+++ b/chrome/browser/engagement/site_engagement_service.cc
@@ -36,6 +36,10 @@
 #include "content/public/browser/web_contents.h"
 #include "url/gurl.h"
 
+#if defined(OS_ANDROID)
+#include "chrome/browser/engagement/site_engagement_service_android.h"
+#endif
+
 namespace {
 
 const int FOUR_WEEKS_IN_DAYS = 28;
@@ -238,6 +242,17 @@
   return total_score;
 }
 
+#if defined(OS_ANDROID)
+SiteEngagementServiceAndroid* SiteEngagementService::GetAndroidService() const {
+  return android_service_.get();
+}
+
+void SiteEngagementService::SetAndroidService(
+    std::unique_ptr<SiteEngagementServiceAndroid> android_service) {
+  android_service_ = std::move(android_service);
+}
+#endif
+
 SiteEngagementService::SiteEngagementService(Profile* profile,
                                              std::unique_ptr<base::Clock> clock)
     : profile_(profile), clock_(std::move(clock)), weak_factory_(this) {
diff --git a/chrome/browser/engagement/site_engagement_service.h b/chrome/browser/engagement/site_engagement_service.h
index 65e22334..350d30a9 100644
--- a/chrome/browser/engagement/site_engagement_service.h
+++ b/chrome/browser/engagement/site_engagement_service.h
@@ -14,6 +14,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/time/time.h"
+#include "build/build_config.h"
 #include "chrome/browser/engagement/site_engagement_metrics.h"
 #include "chrome/browser/engagement/site_engagement_observer.h"
 #include "components/history/core/browser/history_service_observer.h"
@@ -37,6 +38,10 @@
 class Profile;
 class SiteEngagementScore;
 
+#if defined(OS_ANDROID)
+class SiteEngagementServiceAndroid;
+#endif
+
 class SiteEngagementScoreProvider {
  public:
   // Returns a non-negative integer representing the engagement score of the
@@ -134,6 +139,7 @@
 
  private:
   friend class SiteEngagementObserver;
+  friend class SiteEngagementServiceAndroid;
   FRIEND_TEST_ALL_PREFIXES(SiteEngagementServiceTest, CheckHistograms);
   FRIEND_TEST_ALL_PREFIXES(SiteEngagementServiceTest, CleanupEngagementScores);
   FRIEND_TEST_ALL_PREFIXES(SiteEngagementServiceTest,
@@ -160,6 +166,13 @@
   FRIEND_TEST_ALL_PREFIXES(SiteEngagementServiceTest, GetScoreFromSettings);
   FRIEND_TEST_ALL_PREFIXES(AppBannerSettingsHelperTest, SiteEngagementTrigger);
 
+#if defined(OS_ANDROID)
+  // Shim class to expose the service to Java.
+  SiteEngagementServiceAndroid* GetAndroidService() const;
+  void SetAndroidService(
+      std::unique_ptr<SiteEngagementServiceAndroid> android_service);
+#endif
+
   // Only used in tests.
   SiteEngagementService(Profile* profile, std::unique_ptr<base::Clock> clock);
 
@@ -253,6 +266,10 @@
   // The clock used to vend times.
   std::unique_ptr<base::Clock> clock_;
 
+#if defined(OS_ANDROID)
+  std::unique_ptr<SiteEngagementServiceAndroid> android_service_;
+#endif
+
   // Metrics are recorded at non-incognito browser startup, and then
   // approximately once per hour thereafter. Store the local time at which
   // metrics were previously uploaded: the first event which affects any
diff --git a/chrome/browser/engagement/site_engagement_service_android.cc b/chrome/browser/engagement/site_engagement_service_android.cc
new file mode 100644
index 0000000..9d71bf9
--- /dev/null
+++ b/chrome/browser/engagement/site_engagement_service_android.cc
@@ -0,0 +1,81 @@
+// Copyright 2016 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/engagement/site_engagement_service_android.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/memory/ptr_util.h"
+#include "chrome/browser/profiles/profile_android.h"
+#include "jni/SiteEngagementService_jni.h"
+#include "url/gurl.h"
+
+using base::android::JavaParamRef;
+
+// static
+bool SiteEngagementServiceAndroid::Register(JNIEnv* env) {
+  return RegisterNativesImpl(env);
+}
+
+// static
+const base::android::ScopedJavaGlobalRef<jobject>&
+SiteEngagementServiceAndroid::GetOrCreate(JNIEnv* env,
+                                          SiteEngagementService* service) {
+  SiteEngagementServiceAndroid* android_service = service->GetAndroidService();
+  if (!android_service) {
+    service->SetAndroidService(
+        base::MakeUnique<SiteEngagementServiceAndroid>(env, service));
+    android_service = service->GetAndroidService();
+  }
+
+  return android_service->java_service_;
+}
+
+SiteEngagementServiceAndroid::SiteEngagementServiceAndroid(
+    JNIEnv* env,
+    SiteEngagementService* service)
+    : service_(service) {
+  java_service_.Reset(Java_SiteEngagementService_create(
+      env, reinterpret_cast<uintptr_t>(this)));
+}
+
+SiteEngagementServiceAndroid::~SiteEngagementServiceAndroid() {
+  Java_SiteEngagementService_onNativeDestroyed(
+      base::android::AttachCurrentThread(), java_service_);
+  java_service_.Reset();
+}
+
+double SiteEngagementServiceAndroid::GetScore(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& caller,
+    const JavaParamRef<jstring>& jurl) const {
+  if (!jurl)
+    return 0;
+
+  return service_->GetScore(
+      GURL(base::android::ConvertJavaStringToUTF16(env, jurl)));
+}
+
+void SiteEngagementServiceAndroid::ResetScoreForURL(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& caller,
+    const JavaParamRef<jstring>& jurl,
+    double score) {
+  if (jurl) {
+    service_->ResetScoreForURL(
+        GURL(base::android::ConvertJavaStringToUTF16(env, jurl)), score);
+  }
+}
+
+base::android::ScopedJavaLocalRef<jobject> SiteEngagementServiceForProfile(
+    JNIEnv* env,
+    const JavaParamRef<jclass>& clazz,
+    const JavaParamRef<jobject>& jprofile) {
+  Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
+  SiteEngagementService* service = SiteEngagementService::Get(profile);
+  DCHECK(service);
+
+  return base::android::ScopedJavaLocalRef<jobject>(
+      SiteEngagementServiceAndroid::GetOrCreate(env, service));
+}
diff --git a/chrome/browser/engagement/site_engagement_service_android.h b/chrome/browser/engagement/site_engagement_service_android.h
new file mode 100644
index 0000000..c68906a
--- /dev/null
+++ b/chrome/browser/engagement/site_engagement_service_android.h
@@ -0,0 +1,51 @@
+// Copyright 2016 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_ENGAGEMENT_SITE_ENGAGEMENT_SERVICE_ANDROID_H_
+#define CHROME_BROWSER_ENGAGEMENT_SITE_ENGAGEMENT_SERVICE_ANDROID_H_
+
+#include <jni.h>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/macros.h"
+#include "chrome/browser/engagement/site_engagement_service.h"
+
+// Wrapper class to expose the Site Engagement Service to Java. This object is
+// owned by the |service_| which it wraps, and is lazily created when
+// a Java-side SiteEngagementService is constructed. Once created, all future
+// Java-side requests for a SiteEngagementService will use the same native
+// object.
+//
+// This class may only be used on the UI thread.
+class SiteEngagementServiceAndroid {
+ public:
+  static bool Register(JNIEnv* env);
+
+  // Returns the Java-side SiteEngagementService object corresponding to
+  // |service|.
+  static const base::android::ScopedJavaGlobalRef<jobject>& GetOrCreate(
+      JNIEnv* env,
+      SiteEngagementService* service);
+
+  SiteEngagementServiceAndroid(JNIEnv* env, SiteEngagementService* service);
+
+  ~SiteEngagementServiceAndroid();
+
+  double GetScore(JNIEnv* env,
+                  const base::android::JavaParamRef<jobject>& caller,
+                  const base::android::JavaParamRef<jstring>& jurl) const;
+
+  void ResetScoreForURL(JNIEnv* env,
+                        const base::android::JavaParamRef<jobject>& caller,
+                        const base::android::JavaParamRef<jstring>& jurl,
+                        double score);
+
+ private:
+  base::android::ScopedJavaGlobalRef<jobject> java_service_;
+  SiteEngagementService* service_;
+
+  DISALLOW_COPY_AND_ASSIGN(SiteEngagementServiceAndroid);
+};
+
+#endif  // CHROME_BROWSER_ENGAGEMENT_SITE_ENGAGEMENT_SERVICE_ANDROID_H_