| // 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 "content/public/browser/web_contents.h" |
| #include "ui/android/window_android.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 is_one_time = PermissionUtil::DoesSupportTemporaryGrants( |
| permission_prompt_->GetContentSettingType(0)); |
| j_delegate_.Reset(Java_PermissionDialogDelegate_create( |
| env, reinterpret_cast<uintptr_t>(owner), |
| web_contents->GetTopLevelNativeWindow()->GetJavaObject(), |
| permission_prompt_->GetContentSettingTypes(env), |
| PermissionsClient::Get()->MapToJavaDrawableId( |
| permission_prompt_->GetIconId()), |
| ConvertUTF16ToJavaString( |
| env, permission_prompt_->GetAnnotatedMessageText().text), |
| permission_prompt_->GetBoldRanges(env), |
| permission_prompt_->GetPositiveButtonText(env, is_one_time), |
| permission_prompt_->GetNegativeButtonText(env, is_one_time), |
| permission_prompt_->GetPositiveEphemeralButtonText(env, is_one_time), |
| /*showPositiveNonEphemeralAsFirstButton=*/is_one_time, |
| static_cast<int>(permission_prompt_->GetEmbeddedPromptVariant()))); |
| } |
| |
| void PermissionDialogJavaDelegate::CreateDialog( |
| content::WebContents* web_contents) { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| // Send the Java delegate to the Java PermissionDialogController for display. |
| // When the Java delegate is no longer needed it will in turn reset the native |
| // java delegate (PermissionDialogJavaDelegate). |
| 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_); |
| } |
| |
| void PermissionDialogJavaDelegate::NotifyPermissionAllowed() { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| Java_PermissionDialogDelegate_notifyPermissionAllowed(env, j_delegate_); |
| } |
| |
| void PermissionDialogJavaDelegate::UpdateDialog() { |
| CHECK(permission_prompt_->GetEmbeddedPromptVariant() != |
| EmbeddedPermissionPromptFlowModel::Variant::kUninitialized); |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| bool is_one_time = PermissionUtil::DoesSupportTemporaryGrants( |
| permission_prompt_->GetContentSettingType(0)); |
| Java_PermissionDialogDelegate_updateDialog( |
| env, j_delegate_, permission_prompt_->GetContentSettingTypes(env), |
| PermissionsClient::Get()->MapToJavaDrawableId( |
| permission_prompt_->GetIconId()), |
| ConvertUTF16ToJavaString( |
| env, permission_prompt_->GetAnnotatedMessageText().text), |
| permission_prompt_->GetBoldRanges(env), |
| permission_prompt_->GetPositiveButtonText(env, is_one_time), |
| permission_prompt_->GetNegativeButtonText(env, is_one_time), |
| permission_prompt_->GetPositiveEphemeralButtonText(env, is_one_time), |
| /*showPositiveNonEphemeralAsFirstButton=*/is_one_time, |
| static_cast<int>(permission_prompt_->GetEmbeddedPromptVariant())); |
| } |
| |
| // static |
| std::unique_ptr<PermissionDialogDelegate> 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 nullptr; |
| } |
| std::unique_ptr<PermissionDialogJavaDelegate> java_delegate( |
| std::make_unique<PermissionDialogJavaDelegate>(permission_prompt)); |
| return std::make_unique<PermissionDialogDelegate>( |
| web_contents, permission_prompt, std::move(java_delegate)); |
| } |
| |
| // static |
| std::unique_ptr<PermissionDialogDelegate> |
| PermissionDialogDelegate::CreateForTesting( |
| content::WebContents* web_contents, |
| PermissionPromptAndroid* permission_prompt, |
| std::unique_ptr<PermissionDialogJavaDelegate> java_delegate) { |
| return std::make_unique<PermissionDialogDelegate>( |
| web_contents, permission_prompt, std::move(java_delegate)); |
| } |
| |
| void PermissionDialogDelegate::Accept(JNIEnv* env) { |
| CHECK(permission_prompt_); |
| permission_prompt_->Accept(); |
| } |
| |
| void PermissionDialogDelegate::AcceptThisTime(JNIEnv* env) { |
| CHECK(permission_prompt_); |
| permission_prompt_->AcceptThisTime(); |
| } |
| |
| void PermissionDialogDelegate::Acknowledge(JNIEnv* env) { |
| CHECK(permission_prompt_); |
| permission_prompt_->Acknowledge(); |
| } |
| |
| void PermissionDialogDelegate::Deny(JNIEnv* env) { |
| CHECK(permission_prompt_); |
| permission_prompt_->Deny(); |
| } |
| |
| void PermissionDialogDelegate::Resumed(JNIEnv* env) { |
| CHECK(permission_prompt_); |
| permission_prompt_->Resumed(); |
| } |
| |
| void PermissionDialogDelegate::SystemSettingsShown(JNIEnv* env) { |
| CHECK(permission_prompt_); |
| permission_prompt_->SystemSettingsShown(); |
| } |
| |
| void PermissionDialogDelegate::SystemPermissionResolved(JNIEnv* env, |
| bool accepted) { |
| CHECK(permission_prompt_); |
| permission_prompt_->SystemPermissionResolved(accepted); |
| } |
| |
| void PermissionDialogDelegate::Dismissed(JNIEnv* env, |
| 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)); |
| } |
| |
| if (!permission_prompt_->IsShowing()) { |
| // This probably happens synchronously when creating the |
| // `PermissionPromptAndroid` fails, and the `view_` of |
| // `PermissionRequestManager` won't be ready yet. It can mess up here, this |
| // prompt will be assigned to the 'view_' of the 'PermissionRequestManager.' |
| // But, all the underlying data associated with it will get wiped. |
| // So, we destroy the Java delegate and use the `IsJavaDelegateDestroyed` |
| // signal as a way to tell if the `PermissionPrompt` creation failed. |
| DestroyJavaDelegate(); |
| } |
| permission_prompt_->Closing(); |
| } |
| |
| void PermissionDialogDelegate::Destroy(JNIEnv* env) { |
| DestroyJavaDelegate(); |
| } |
| |
| void PermissionDialogDelegate::NotifyPermissionAllowed() { |
| CHECK(!IsJavaDelegateDestroyed()); |
| java_delegate_->NotifyPermissionAllowed(); |
| } |
| |
| void PermissionDialogDelegate::UpdateDialog() { |
| CHECK(!IsJavaDelegateDestroyed()); |
| java_delegate_->UpdateDialog(); |
| } |
| |
| 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. |
| java_delegate_->CreateJavaDelegate(web_contents, this); |
| // Open the Permission Dialog. |
| java_delegate_->CreateDialog(web_contents); |
| // Note: `java_delegate_` can be destroyed after this line, if Java |
| // counterpart fails to show the dialog. |
| } |
| |
| PermissionDialogDelegate::~PermissionDialogDelegate() { |
| // When the owning class is destroyed, ensure that any active java delegate |
| // associated with the class is destroyed. |
| if (!IsJavaDelegateDestroyed()) { |
| DismissDialog(); |
| } |
| } |
| |
| void PermissionDialogDelegate::DismissDialog() { |
| // `java_delegate_` is owned by `this` and will be freed before `this`. During |
| // the gap, it's still possible that `this` receives some dismiss signals but |
| // should do nothing. |
| if (!IsJavaDelegateDestroyed()) { |
| java_delegate_->DismissDialog(); |
| } |
| } |
| |
| void PermissionDialogDelegate::PrimaryPageChanged(content::Page& page) { |
| DismissDialog(); |
| } |
| |
| void PermissionDialogDelegate::WebContentsDestroyed() { |
| DismissDialog(); |
| } |
| |
| void PermissionDialogDelegate::OnGeolocationAccuracySelected(JNIEnv* env, |
| bool isPrecise) { |
| CHECK(permission_prompt_); |
| |
| GeolocationPromptOptions geolocation_options; |
| geolocation_options.selected_precise = isPrecise; |
| |
| PromptOptions prompt_options = geolocation_options; |
| |
| permission_prompt_->SetPromptOptions(std::move(prompt_options)); |
| } |
| |
| static jint JNI_PermissionDialogDelegate_GetRequestTypeEnumSize(JNIEnv* env) { |
| return static_cast<int>(RequestType::kMaxValue) + 1; |
| } |
| |
| } // namespace permissions |