// Copyright 2016 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/permissions/android/permission_prompt/permission_dialog_delegate.h"
#include <string_view>
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/android/permission_prompt/permission_prompt_android.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/permission_util.h"
#include "components/permissions/permissions_client.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/web_contents.h"
#include "ui/android/window_android.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/gfx/android/java_bitmap.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/permissions/android/jni_headers/PermissionDialogController_jni.h"
#include "components/permissions/android/jni_headers/PermissionDialogDelegate_jni.h"
using base::android::ConvertUTF16ToJavaString;
namespace permissions {
PermissionDialogJavaDelegate::PermissionDialogJavaDelegate(
PermissionPromptAndroid* permission_prompt)
: permission_prompt_(permission_prompt) {}
PermissionDialogJavaDelegate::~PermissionDialogJavaDelegate() = default;
void PermissionDialogJavaDelegate::CreateJavaDelegate(
content::WebContents* web_contents,
PermissionDialogDelegate* owner) {
// Create our Java counterpart, which manages the lifetime of
// PermissionDialogDelegate.
JNIEnv* env = base::android::AttachCurrentThread();
bool isOneTime =
base::FeatureList::IsEnabled(permissions::features::kOneTimePermission) &&
PermissionUtil::DoesSupportTemporaryGrants(
permission_prompt_->GetContentSettingType(0));
base::android::ScopedJavaLocalRef<jstring> positiveButtonText;
base::android::ScopedJavaLocalRef<jstring> negativeButtonText;
base::android::ScopedJavaLocalRef<jstring> positiveEphemeralButtonText;
bool showPositiveNonEphemeralAsFirstButton = false;
if (isOneTime) {
positiveButtonText = ConvertUTF16ToJavaString(
env, l10n_util::GetStringUTF16(
permissions::feature_params::kUseWhileVisitingLanguage.Get()
? IDS_PERMISSION_ALLOW_WHILE_VISITING
: IDS_PERMISSION_ALLOW_EVERY_VISIT));
negativeButtonText = ConvertUTF16ToJavaString(
env, l10n_util::GetStringUTF16(
permissions::feature_params::kUseStrongerPromptLanguage.Get()
? IDS_PERMISSION_NEVER_ALLOW
: IDS_PERMISSION_DONT_ALLOW));
positiveEphemeralButtonText = ConvertUTF16ToJavaString(
env, l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW_THIS_TIME));
showPositiveNonEphemeralAsFirstButton =
permissions::feature_params::kShowAllowAlwaysAsFirstButton.Get();
} else {
positiveButtonText = ConvertUTF16ToJavaString(
env, l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW));
negativeButtonText = ConvertUTF16ToJavaString(
env, l10n_util::GetStringUTF16(IDS_PERMISSION_DENY));
positiveEphemeralButtonText =
ConvertUTF16ToJavaString(env, std::u16string_view());
}
std::vector<int> content_settings_types;
for (size_t i = 0; i < permission_prompt_->PermissionCount(); ++i) {
content_settings_types.push_back(
static_cast<int>(permission_prompt_->GetContentSettingType(i)));
}
PermissionRequest::AnnotatedMessageText annotatedMessageText =
permission_prompt_->GetAnnotatedMessageText();
std::vector<int> bolded_ranges;
for (auto [start, end] : annotatedMessageText.bolded_ranges) {
bolded_ranges.push_back(base::checked_cast<int>(start));
bolded_ranges.push_back(base::checked_cast<int>(end));
}
j_delegate_.Reset(Java_PermissionDialogDelegate_create(
env, reinterpret_cast<uintptr_t>(owner),
web_contents->GetTopLevelNativeWindow()->GetJavaObject(),
base::android::ToJavaIntArray(env, content_settings_types),
PermissionsClient::Get()->MapToJavaDrawableId(
permission_prompt_->GetIconId()),
ConvertUTF16ToJavaString(env, annotatedMessageText.text),
base::android::ToJavaIntArray(env, bolded_ranges), positiveButtonText,
negativeButtonText, positiveEphemeralButtonText,
showPositiveNonEphemeralAsFirstButton));
}
void PermissionDialogJavaDelegate::CreateDialog(
content::WebContents* web_contents) {
JNIEnv* env = base::android::AttachCurrentThread();
// Send the Java delegate to the Java PermissionDialogController for display.
// The controller takes over lifetime management; when the Java delegate is no
// longer needed it will in turn free the native delegate
// (PermissionDialogDelegate).
Java_PermissionDialogController_createDialog(env, j_delegate_);
if (permission_prompt_->ShouldUseRequestingOriginFavicon()) {
// In order to update the dialog, we need to make sure it has been created
// before.
GetAndUpdateRequestingOriginFavicon(web_contents);
}
}
void PermissionDialogJavaDelegate::GetAndUpdateRequestingOriginFavicon(
content::WebContents* web_contents) {
favicon::FaviconService* favicon_service =
PermissionsClient::Get()->GetFaviconService(
web_contents->GetBrowserContext());
CHECK(favicon_service);
JNIEnv* env = base::android::AttachCurrentThread();
int iconSizeInPx =
Java_PermissionDialogDelegate_getIconSizeInPx(env, j_delegate_);
// Fetching requesting origin favicon.
// Fetch raw favicon to set |fallback_to_host|=true since we otherwise might
// not get a result if the user never visited the root URL of |site|.
favicon_service->GetRawFaviconForPageURL(
permission_prompt_->GetRequestingOrigin(),
{favicon_base::IconType::kFavicon}, iconSizeInPx,
/*fallback_to_host=*/true,
base::BindOnce(
&PermissionDialogJavaDelegate::OnRequestingOriginFaviconLoaded,
base::Unretained(this)),
&favicon_tracker_);
}
void PermissionDialogJavaDelegate::OnRequestingOriginFaviconLoaded(
const favicon_base::FaviconRawBitmapResult& favicon_result) {
if (favicon_result.is_valid()) {
gfx::Image image =
gfx::Image::CreateFrom1xPNGBytes(favicon_result.bitmap_data);
const SkBitmap* bitmap = image.ToSkBitmap();
JNIEnv* env = base::android::AttachCurrentThread();
Java_PermissionDialogDelegate_updateIcon(env, j_delegate_,
gfx::ConvertToJavaBitmap(*bitmap));
}
}
void PermissionDialogJavaDelegate::DismissDialog() {
JNIEnv* env = base::android::AttachCurrentThread();
Java_PermissionDialogDelegate_dismissFromNative(env, j_delegate_);
}
// static
void PermissionDialogDelegate::Create(
content::WebContents* web_contents,
PermissionPromptAndroid* permission_prompt) {
CHECK(web_contents);
// If we don't have a window, just act as though the prompt was dismissed.
if (!web_contents->GetTopLevelNativeWindow()) {
permission_prompt->Closing();
return;
}
std::unique_ptr<PermissionDialogJavaDelegate> java_delegate(
std::make_unique<PermissionDialogJavaDelegate>(permission_prompt));
new PermissionDialogDelegate(web_contents, permission_prompt,
std::move(java_delegate));
}
// static
PermissionDialogDelegate* PermissionDialogDelegate::CreateForTesting(
content::WebContents* web_contents,
PermissionPromptAndroid* permission_prompt,
std::unique_ptr<PermissionDialogJavaDelegate> java_delegate) {
return new PermissionDialogDelegate(web_contents, permission_prompt,
std::move(java_delegate));
}
void PermissionDialogDelegate::Accept(JNIEnv* env,
const JavaParamRef<jobject>& obj) {
CHECK(permission_prompt_);
permission_prompt_->Accept();
}
void PermissionDialogDelegate::AcceptThisTime(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
CHECK(permission_prompt_);
permission_prompt_->AcceptThisTime();
}
void PermissionDialogDelegate::Cancel(JNIEnv* env,
const JavaParamRef<jobject>& obj) {
CHECK(permission_prompt_);
permission_prompt_->Deny();
}
void PermissionDialogDelegate::Dismissed(JNIEnv* env,
const JavaParamRef<jobject>& obj,
int dismissalType) {
CHECK(permission_prompt_);
std::vector<ContentSettingsType> content_settings_types;
for (size_t i = 0; i < permission_prompt_->PermissionCount(); ++i) {
ContentSettingsType type = permission_prompt_->GetContentSettingType(i);
// Not all request types have an associated ContentSettingsType.
if (type == ContentSettingsType::DEFAULT) {
break;
}
content_settings_types.push_back(type);
}
if (content_settings_types.size() == permission_prompt_->PermissionCount()) {
PermissionUmaUtil::RecordDismissalType(
content_settings_types, permission_prompt_->GetPromptDisposition(),
static_cast<DismissalType>(dismissalType));
}
permission_prompt_->Closing();
}
void PermissionDialogDelegate::Destroy(JNIEnv* env,
const JavaParamRef<jobject>& obj) {
delete this;
}
PermissionDialogDelegate::PermissionDialogDelegate(
content::WebContents* web_contents,
PermissionPromptAndroid* permission_prompt,
std::unique_ptr<PermissionDialogJavaDelegate> java_delegate)
: content::WebContentsObserver(web_contents),
permission_prompt_(permission_prompt),
java_delegate_(std::move(java_delegate)) {
CHECK(java_delegate_);
// Create our Java counterpart, which manages our lifetime.
java_delegate_->CreateJavaDelegate(web_contents, this);
// Open the Permission Dialog.
java_delegate_->CreateDialog(web_contents);
}
PermissionDialogDelegate::~PermissionDialogDelegate() = default;
void PermissionDialogDelegate::DismissDialog() {
java_delegate_->DismissDialog();
}
void PermissionDialogDelegate::PrimaryPageChanged(content::Page& page) {
DismissDialog();
}
void PermissionDialogDelegate::WebContentsDestroyed() {
DismissDialog();
}
static jint JNI_PermissionDialogDelegate_GetRequestTypeEnumSize(JNIEnv* env) {
return static_cast<int>(RequestType::kMaxValue) + 1;
}
} // namespace permissions