// 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) {
  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();
  DCHECK(window_android);

  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;
  }
  JNIEnv* env = base::android::AttachCurrentThread();
  auto* window_android = web_contents->GetNativeView()->GetWindowAndroid();
  DCHECK(window_android);
  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)
