// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/content_capture/android/onscreen_content_provider_android.h"
#include <utility>
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "components/content_capture/common/content_capture_features.h"
#include "content/public/browser/web_contents.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/content_capture/android/jni_headers/ContentCaptureData_jni.h"
#include "components/content_capture/android/jni_headers/ContentCaptureFrame_jni.h"
#include "components/content_capture/android/jni_headers/OnscreenContentProvider_jni.h"
using base::android::AttachCurrentThread;
using base::android::ConvertUTF16ToJavaString;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaRef;
using base::android::ScopedJavaLocalRef;
using base::android::ToJavaLongArray;
namespace content_capture {
namespace {
ScopedJavaLocalRef<jobject> ToJavaObjectOfContentCaptureData(
JNIEnv* env,
const ContentCaptureData& data,
const JavaRef<jobject>& parent,
int offset_y) {
ScopedJavaLocalRef<jstring> jvalue =
ConvertUTF16ToJavaString(env, data.value);
ScopedJavaLocalRef<jobject> jdata =
Java_ContentCaptureData_createContentCaptureData(
env, parent, data.id, jvalue, data.bounds.x(),
data.bounds.y() + offset_y, data.bounds.width(),
data.bounds.height());
if (jdata.is_null())
return jdata;
for (const auto& child : data.children) {
ToJavaObjectOfContentCaptureData(env, child, jdata, offset_y);
}
return jdata;
}
ScopedJavaLocalRef<jobject> ToJavaObjectOfContentCaptureFrame(
JNIEnv* env,
const ContentCaptureFrame& data,
int offset_y) {
ScopedJavaLocalRef<jstring> jurl = ConvertUTF16ToJavaString(env, data.url);
ScopedJavaLocalRef<jstring> jtitle;
if (!data.title.empty())
jtitle = ConvertUTF16ToJavaString(env, data.title);
ScopedJavaLocalRef<jstring> jfavicon;
if (!data.favicon.empty())
jfavicon = ConvertUTF8ToJavaString(env, data.favicon);
ScopedJavaLocalRef<jobject> jdata =
Java_ContentCaptureFrame_createContentCaptureFrame(
env, data.id, jurl, data.bounds.x(), data.bounds.y() + offset_y,
data.bounds.width(), data.bounds.height(), jtitle, jfavicon);
if (jdata.is_null())
return jdata;
for (const auto& child : data.children) {
ToJavaObjectOfContentCaptureData(env, child, jdata, offset_y);
}
return jdata;
}
ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfContentCaptureFrame(
JNIEnv* env,
const ContentCaptureSession& session,
int offset_y) {
jobjectArray joa =
env->NewObjectArray(session.size(), jni_zero::g_object_class, nullptr);
base::android::CheckException(env);
for (size_t i = 0; i < session.size(); ++i) {
ScopedJavaLocalRef<jobject> item =
ToJavaObjectOfContentCaptureFrame(env, session[i], offset_y);
env->SetObjectArrayElement(joa, i, item.obj());
}
return ScopedJavaLocalRef<jobjectArray>(env, joa);
}
} // namespace
static jlong JNI_OnscreenContentProvider_Init(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jobject>& jwebContents) {
auto* web_contents = content::WebContents::FromJavaWebContents(jwebContents);
DCHECK(web_contents);
auto* provider = new content_capture::OnscreenContentProviderAndroid(
env, jcaller, web_contents);
return reinterpret_cast<intptr_t>(provider);
}
OnscreenContentProviderAndroid::OnscreenContentProviderAndroid(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jobject,
content::WebContents* web_contents)
: java_ref_(jobject) {
AttachToWebContents(web_contents);
}
OnscreenContentProviderAndroid::~OnscreenContentProviderAndroid() = default;
void OnscreenContentProviderAndroid::DidCaptureContent(
const ContentCaptureSession& parent_session,
const ContentCaptureFrame& data) {
JNIEnv* env = AttachCurrentThread();
DCHECK(java_ref_.obj());
auto* web_contents = GetWebContents();
DCHECK(web_contents);
const int offset_y = Java_OnscreenContentProvider_getOffsetY(
env, java_ref_, web_contents->GetJavaWebContents());
ScopedJavaLocalRef<jobject> jdata =
ToJavaObjectOfContentCaptureFrame(env, data, offset_y);
if (jdata.is_null())
return;
Java_OnscreenContentProvider_didCaptureContent(
env, java_ref_,
ToJavaArrayOfContentCaptureFrame(env, parent_session, offset_y), jdata);
}
void OnscreenContentProviderAndroid::DidUpdateContent(
const ContentCaptureSession& parent_session,
const ContentCaptureFrame& data) {
JNIEnv* env = AttachCurrentThread();
DCHECK(java_ref_.obj());
auto* web_contents = GetWebContents();
DCHECK(web_contents);
const int offset_y = Java_OnscreenContentProvider_getOffsetY(
env, java_ref_, web_contents->GetJavaWebContents());
ScopedJavaLocalRef<jobject> jdata =
ToJavaObjectOfContentCaptureFrame(env, data, offset_y);
if (jdata.is_null())
return;
Java_OnscreenContentProvider_didUpdateContent(
env, java_ref_,
ToJavaArrayOfContentCaptureFrame(env, parent_session, offset_y), jdata);
}
void OnscreenContentProviderAndroid::DidRemoveContent(
const ContentCaptureSession& session,
const std::vector<int64_t>& data) {
JNIEnv* env = AttachCurrentThread();
DCHECK(java_ref_.obj());
auto* web_contents = GetWebContents();
DCHECK(web_contents);
const int offset_y = Java_OnscreenContentProvider_getOffsetY(
env, java_ref_, web_contents->GetJavaWebContents());
Java_OnscreenContentProvider_didRemoveContent(
env, java_ref_, ToJavaArrayOfContentCaptureFrame(env, session, offset_y),
ToJavaLongArray(env, data));
}
void OnscreenContentProviderAndroid::DidRemoveSession(
const ContentCaptureSession& session) {
JNIEnv* env = AttachCurrentThread();
DCHECK(java_ref_.obj());
auto* web_contents = GetWebContents();
DCHECK(web_contents);
const int offset_y = Java_OnscreenContentProvider_getOffsetY(
env, java_ref_, web_contents->GetJavaWebContents());
Java_OnscreenContentProvider_didRemoveSession(
env, java_ref_, ToJavaArrayOfContentCaptureFrame(env, session, offset_y));
}
void OnscreenContentProviderAndroid::DidUpdateTitle(
const ContentCaptureFrame& main_frame) {
JNIEnv* env = AttachCurrentThread();
DCHECK(java_ref_.obj());
auto* web_contents = GetWebContents();
DCHECK(web_contents);
const int offset_y = Java_OnscreenContentProvider_getOffsetY(
env, java_ref_, web_contents->GetJavaWebContents());
ScopedJavaLocalRef<jobject> jdata =
ToJavaObjectOfContentCaptureFrame(env, main_frame, offset_y);
if (jdata.is_null())
return;
Java_OnscreenContentProvider_didUpdateTitle(env, java_ref_, jdata);
}
void OnscreenContentProviderAndroid::DidUpdateFavicon(
const ContentCaptureFrame& main_frame) {
JNIEnv* env = AttachCurrentThread();
DCHECK(java_ref_.obj());
auto* web_contents = GetWebContents();
DCHECK(web_contents);
const int offset_y = Java_OnscreenContentProvider_getOffsetY(
env, java_ref_, web_contents->GetJavaWebContents());
ScopedJavaLocalRef<jobject> jdata =
ToJavaObjectOfContentCaptureFrame(env, main_frame, offset_y);
if (jdata.is_null())
return;
Java_OnscreenContentProvider_didUpdateFavicon(env, java_ref_, jdata);
}
bool OnscreenContentProviderAndroid::ShouldCapture(const GURL& url) {
// Capture all urls for experiment, the url will be checked
// before the content is sent to the consumers.
if (features::ShouldTriggerContentCaptureForExperiment())
return true;
JNIEnv* env = AttachCurrentThread();
return Java_OnscreenContentProvider_shouldCapture(
env, java_ref_, ConvertUTF8ToJavaString(env, url.spec()));
}
ScopedJavaLocalRef<jobject> OnscreenContentProviderAndroid::GetJavaObject() {
return ScopedJavaLocalRef<jobject>(java_ref_);
}
void OnscreenContentProviderAndroid::OnWebContentsChanged(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jweb_contents) {
if (auto* web_contents =
content::WebContents::FromJavaWebContents(jweb_contents)) {
AttachToWebContents(web_contents);
}
}
void OnscreenContentProviderAndroid::AttachToWebContents(
content::WebContents* web_contents) {
DetachFromWebContents();
OnscreenContentProvider* provider =
OnscreenContentProvider::FromWebContents(web_contents);
if (!provider)
provider = OnscreenContentProvider::Create(web_contents);
provider->AddConsumer(*this);
onscreen_content_provider_ = provider->GetWeakPtr();
}
void OnscreenContentProviderAndroid::DetachFromWebContents() {
if (auto* provider = onscreen_content_provider_.get())
provider->RemoveConsumer(*this);
}
void OnscreenContentProviderAndroid::Destroy(JNIEnv* env) {
DetachFromWebContents();
delete this;
}
content::WebContents* OnscreenContentProviderAndroid::GetWebContents() {
if (auto* provider = onscreen_content_provider_.get())
return provider->web_contents();
return nullptr;
}
} // namespace content_capture