blob: adc2bdefe15a7f58025fb946dd539e7ea663890f [file]
// Copyright 2020 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/android_permission_util.h"
#include <variant>
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/auto_reset.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/location/android/location_settings_impl.h"
#include "components/permissions/android/permission_prompt/permission_prompt_android.h"
#include "components/permissions/permission_request_manager.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/permission_util.h"
#include "content/public/browser/web_contents.h"
#include "third_party/jni_zero/default_conversions.h"
#include "ui/android/window_android.h"
#include "url/android/gurl_android.h"
#include "url/gurl.h"
#include "url/origin.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/permissions/android/jni_headers/AndroidPermissionRequester_jni.h"
#include "components/permissions/android/jni_headers/PermissionUtil_jni.h"
namespace permissions {
namespace {
bool g_is_system_location_setting_enabled_for_test = false;
bool g_are_all_android_permissions_enabled_for_test = false;
// Returns whether the Android location setting is enabled/disabled.
bool IsSystemLocationSettingEnabled() {
if (g_is_system_location_setting_enabled_for_test) {
return true;
}
LocationSettingsImpl location_settings;
return location_settings.IsSystemLocationSettingEnabled();
}
} // namespace
void AppendRequiredAndroidPermissionsForContentSetting(
ContentSettingsType content_settings_type,
std::vector<std::string>* out) {
JNIEnv* env = base::android::AttachCurrentThread();
std::vector<std::string> result =
Java_PermissionUtil_getRequiredAndroidPermissionsForContentSetting(
env, static_cast<int>(content_settings_type));
out->insert(out->end(), std::make_move_iterator(result.begin()),
std::make_move_iterator(result.end()));
}
void AppendOptionalAndroidPermissionsForContentSetting(
ContentSettingsType content_settings_type,
std::vector<std::string>* out) {
JNIEnv* env = base::android::AttachCurrentThread();
std::vector<std::string> result =
Java_PermissionUtil_getOptionalAndroidPermissionsForContentSetting(
env, static_cast<int>(content_settings_type));
out->insert(out->end(), std::make_move_iterator(result.begin()),
std::make_move_iterator(result.end()));
}
bool HasRequiredAndroidPermissionsForContentSetting(
ui::WindowAndroid* window_android,
ContentSettingsType content_settings_type) {
if (!window_android) {
return false;
}
JNIEnv* env = base::android::AttachCurrentThread();
return Java_AndroidPermissionRequester_hasRequiredAndroidPermissionsForContentSetting(
env, window_android, static_cast<int>(content_settings_type));
}
PermissionRepromptState ShouldRepromptUserForPermissions(
content::WebContents* web_contents,
const std::vector<ContentSettingsType>& content_settings_types) {
if (!web_contents) {
return PermissionRepromptState::kCannotShow;
}
auto* window_android = web_contents->GetNativeView()->GetWindowAndroid();
if (!window_android) {
return PermissionRepromptState::kCannotShow;
}
for (ContentSettingsType content_settings_type : content_settings_types) {
if (!HasRequiredAndroidPermissionsForContentSetting(
window_android, content_settings_type)) {
PermissionUmaUtil::RecordMissingPermissionInfobarShouldShow(
true, content_settings_types);
return PermissionRepromptState::kShow;
}
}
permissions::PermissionUmaUtil::RecordMissingPermissionInfobarShouldShow(
false, content_settings_types);
return PermissionRepromptState::kNoNeed;
}
std::vector<ContentSettingsType>
GetContentSettingsWithMissingRequiredAndroidPermissions(
const std::vector<ContentSettingsType>& content_settings_types,
content::WebContents* web_contents) {
auto* window_android = web_contents->GetNativeView()->GetWindowAndroid();
DCHECK(window_android);
std::vector<ContentSettingsType> filtered_types;
for (ContentSettingsType content_settings_type : content_settings_types) {
if (HasRequiredAndroidPermissionsForContentSetting(window_android,
content_settings_type)) {
continue;
}
filtered_types.push_back(content_settings_type);
}
return filtered_types;
}
void AppendRequiredAndOptionalAndroidPermissionsForContentSettings(
const std::vector<ContentSettingsType>& content_settings_types,
std::vector<std::string>& out_required_permissions,
std::vector<std::string>& out_optional_permissions) {
for (ContentSettingsType content_settings_type : content_settings_types) {
permissions::AppendRequiredAndroidPermissionsForContentSetting(
content_settings_type, &out_required_permissions);
permissions::AppendOptionalAndroidPermissionsForContentSetting(
content_settings_type, &out_optional_permissions);
}
}
bool DoesAppLevelSettingsAllowSiteNotifications() {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_PermissionUtil_doesAppLevelSettingsAllowSiteNotifications(env);
}
bool NeedsLocationPermissionForBluetooth(content::WebContents* web_contents) {
JNIEnv* env = base::android::AttachCurrentThread();
auto* window_android = web_contents->GetNativeView()->GetWindowAndroid();
DCHECK(window_android);
return Java_PermissionUtil_needsLocationPermissionForBluetooth(
env, window_android);
}
bool NeedsNearbyDevicesPermissionForBluetooth(
content::WebContents* web_contents) {
JNIEnv* env = base::android::AttachCurrentThread();
auto* window_android = web_contents->GetNativeView()->GetWindowAndroid();
DCHECK(window_android);
return Java_PermissionUtil_needsNearbyDevicesPermissionForBluetooth(
env, window_android);
}
bool NeedsLocationServicesForBluetooth() {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_PermissionUtil_needsLocationServicesForBluetooth(env);
}
bool CanRequestSystemPermissionsForBluetooth(
content::WebContents* web_contents) {
JNIEnv* env = base::android::AttachCurrentThread();
auto* window_android = web_contents->GetNativeView()->GetWindowAndroid();
DCHECK(window_android);
return Java_PermissionUtil_canRequestSystemPermissionsForBluetooth(
env, window_android);
}
bool HasSystemPermission(ContentSettingsType type,
content::WebContents* web_contents) {
if (g_are_all_android_permissions_enabled_for_test) {
return true;
}
if (!web_contents || !web_contents->GetNativeView()) {
return false;
}
if ((type == permissions::PermissionUtil::GetGeolocationType()) &&
!IsSystemLocationSettingEnabled()) {
return false;
}
auto* window_android = web_contents->GetNativeView()->GetWindowAndroid();
if (!window_android) {
return false;
}
return HasRequiredAndroidPermissionsForContentSetting(window_android, type);
}
bool CanRequestSystemPermission(ContentSettingsType type,
content::WebContents* web_contents) {
if (!web_contents || !web_contents->GetNativeView()) {
return false;
}
if ((type == permissions::PermissionUtil::GetGeolocationType()) &&
!IsSystemLocationSettingEnabled()) {
return false;
}
auto* window_android = web_contents->GetNativeView()->GetWindowAndroid();
if (!window_android) {
return false;
}
JNIEnv* env = base::android::AttachCurrentThread();
return Java_PermissionUtil_canRequestSystemPermission(
env, static_cast<int>(type), window_android);
}
void RequestSystemPermissionsForBluetooth(content::WebContents* web_contents) {
JNIEnv* env = base::android::AttachCurrentThread();
auto* window_android = web_contents->GetNativeView()->GetWindowAndroid();
DCHECK(window_android);
// TODO(crbug.com/40255210): Pass the callback from native layer.
Java_PermissionUtil_requestSystemPermissionsForBluetooth(env, window_android,
nullptr);
}
void RequestLocationServices(content::WebContents* web_contents) {
JNIEnv* env = base::android::AttachCurrentThread();
auto* window_android = web_contents->GetNativeView()->GetWindowAndroid();
DCHECK(window_android);
Java_PermissionUtil_requestLocationServices(env, window_android);
}
base::AutoReset<bool> EnableSystemLocationSettingForTesting() {
return base::AutoReset<bool>(&g_is_system_location_setting_enabled_for_test,
true);
}
base::AutoReset<bool> EnableAllAndroidPermissionsForTesting() {
return base::AutoReset<bool>(&g_are_all_android_permissions_enabled_for_test,
true);
}
void ResolvePermissionWithOSPrompt(content::WebContents* web_contents,
ContentSettingsType content_settings_type,
const GURL& requesting_origin) {
DCHECK(web_contents);
auto* window_android = web_contents->GetNativeView()->GetWindowAndroid();
DCHECK(window_android);
JNIEnv* env = base::android::AttachCurrentThread();
Java_PermissionUtil_handlePermissionPromptAllow(
env, window_android, web_contents, requesting_origin,
static_cast<int>(content_settings_type));
}
namespace internal {
bool ResolveNotificationsPermissionRequest(content::WebContents* web_contents,
const GURL& requesting_origin,
ContentSetting setting) {
if (!web_contents) {
return false;
}
permissions::PermissionRequestManager* permission_request_manager =
permissions::PermissionRequestManager::FromWebContents(web_contents);
if (!permission_request_manager) {
return false;
}
if (permission_request_manager->IsRequestInProgress() &&
!permission_request_manager->Requests().empty() &&
permission_request_manager->Requests()[0]->GetContentSettingsType() ==
ContentSettingsType::NOTIFICATIONS &&
url::Origin::Create(
permission_request_manager->Requests()[0]->requesting_origin()) ==
url::Origin::Create(requesting_origin)) {
switch (setting) {
case CONTENT_SETTING_ALLOW:
if (!permission_request_manager->ShouldCurrentRequestUseQuietUI()) {
base::UmaHistogramBoolean(
"Permissions.ClapperLoud.PageInfo.Subscribed", true);
}
permission_request_manager->Accept(/*prompt_options=*/std::monostate());
return true;
case CONTENT_SETTING_BLOCK:
// There are multiple ways to deny the permission request. This
// histogram will track the number of times the user denied the
// permission request by closing the PageInfo.
if (!permission_request_manager->ShouldCurrentRequestUseQuietUI()) {
base::UmaHistogramBoolean("Permissions.ClapperLoud.PageInfo.Closed",
true);
}
permission_request_manager->Deny(/*prompt_options=*/std::monostate());
return true;
case CONTENT_SETTING_DEFAULT:
if (!permission_request_manager->ShouldCurrentRequestUseQuietUI()) {
base::UmaHistogramBoolean("Permissions.ClapperLoud.PageInfo.Reset",
true);
}
// After the user interacts with the reset permission button in
// PageInfo, all previously decided permissions are reset by setting
// them to DEFAULT. There is no a default action or a state for
// permission requests, so we need to explicitly dismiss the request.
permission_request_manager->Dismiss(
/*prompt_options=*/std::monostate());
return true;
default:
// Currently, only ALLOW and BLOCK are supported. In case other actions
// are added in the future, this should be updated.
NOTREACHED();
}
}
return false;
}
void DismissNotificationsPermissionRequest(content::WebContents* web_contents,
const GURL& requesting_origin) {
if (!web_contents) {
return;
}
permissions::PermissionRequestManager* permission_request_manager =
permissions::PermissionRequestManager::FromWebContents(web_contents);
if (!permission_request_manager) {
return;
}
if (permission_request_manager->IsRequestInProgress() &&
!permission_request_manager->Requests().empty() &&
permission_request_manager->Requests()[0]->GetContentSettingsType() ==
ContentSettingsType::NOTIFICATIONS &&
url::Origin::Create(
permission_request_manager->Requests()[0]->requesting_origin()) ==
url::Origin::Create(requesting_origin)) {
permission_request_manager->Dismiss(/*prompt_options=*/std::monostate());
}
}
} // namespace internal
} // namespace permissions
// This method is called when the user clicks on the "Subscribe" button in the
// notifications permission row in PageInfo but did not grant the Android OS
// level permission prompt. Despite the user granted the site-level permission,
// we still need to dismiss the permission request as Chrome doesn't have the
// Android OS level permission and hence the permission request is no longer
// valid.
static void JNI_PermissionUtil_DismissNotificationsPermissionRequest(
JNIEnv* env,
content::WebContents* web_contents,
const GURL& requesting_origin) {
permissions::internal::DismissNotificationsPermissionRequest(
web_contents, requesting_origin);
}
static bool JNI_PermissionUtil_ResolveNotificationsPermissionRequest(
JNIEnv* env,
content::WebContents* web_contents,
const GURL& requesting_origin,
int32_t content_setting) {
ContentSetting setting = static_cast<ContentSetting>(content_setting);
return permissions::internal::ResolveNotificationsPermissionRequest(
web_contents, requesting_origin, setting);
}
// TODO(crbug.com/463333225): Clean this provisional function name up if
// Clapper is launched or removed.
//
// This is called when the quiet icon is replaced by another icon in the
// omnibox.
static void JNI_PermissionUtil_NotifyQuietIconDismissed(
JNIEnv* env,
content::WebContents* web_contents) {
if (!web_contents) {
return;
}
permissions::PermissionRequestManager* permission_request_manager =
permissions::PermissionRequestManager::FromWebContents(web_contents);
if (permission_request_manager &&
permission_request_manager->IsRequestInProgress()) {
auto* prompt = permission_request_manager->GetCurrentPrompt();
if (prompt && prompt->GetPromptDisposition() ==
permissions::PermissionPromptDisposition::
LOCATION_BAR_LEFT_QUIET_ICON) {
permission_request_manager->Ignore(/*prompt_options=*/std::monostate());
}
}
}
DEFINE_JNI(PermissionUtil)
DEFINE_JNI(AndroidPermissionRequester)