// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/web_contents/web_contents_view_android.h"

#include <memory>
#include <optional>
#include <utility>

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/check.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/notimplemented.h"
#include "base/rand_util.h"
#include "cc/layers/layer.h"
#include "cc/slim/layer.h"
#include "components/input/features.h"
#include "content/browser/accessibility/browser_accessibility_manager_android.h"
#include "content/browser/android/content_ui_event_handler.h"
#include "content/browser/android/drop_data_android.h"
#include "content/browser/android/gesture_listener_manager.h"
#include "content/browser/android/select_popup.h"
#include "content/browser/android/selection/selection_popup_controller.h"
#include "content/browser/navigation_transitions/back_forward_transition_animation_manager_android.h"
#include "content/browser/navigation_transitions/blur_transition_animation_manager.h"
#include "content/browser/renderer_host/render_view_host_factory.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/android/synchronous_compositor.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/drop_data.h"
#include "net/base/mime_util.h"
#include "ui/android/overscroll_refresh_handler.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/clipboard_constants.h"
#include "ui/base/clipboard/file_info.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/ui_base_features.h"
#include "ui/display/display_util.h"
#include "ui/display/screen.h"
#include "ui/events/android/drag_event_android.h"
#include "ui/events/android/gesture_event_android.h"
#include "ui/events/android/key_event_android.h"
#include "ui/events/android/motion_event_android.h"
#include "ui/gfx/android/java_bitmap.h"
#include "ui/gfx/android/view_configuration.h"
#include "ui/gfx/image/image_skia.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "content/public/android/jar_jni/DragEvent_jni.h"

using base::android::AppendJavaStringArrayToStringVector;
using base::android::AttachCurrentThread;
using base::android::ConvertJavaStringToUTF16;
using base::android::ScopedJavaLocalRef;

namespace content {

namespace {

constexpr char16_t kMimeTypeIntent[] = u"text/vnd.android.intent";
constexpr char16_t kMimeTypeChromeLink[] = u"chrome/link";

WebContentsViewAndroid::RenderWidgetHostViewCreateFunction
    g_create_render_widget_host_view = nullptr;

// Returns the minimum distance in DIPs, for drag event being considered as an
// intentional drag.
int DragMovementThresholdDip() {
  static int radius = features::kTouchDragMovementThresholdDip.Get();
  return radius;
}

// True if we want to disable Android native event batching and use
// compositor event queue.
bool ShouldRequestUnbufferedDispatch() {
  static bool should_request_unbuffered_dispatch =
      !GetContentClient()->UsingSynchronousCompositing() &&
      !base::FeatureList::IsEnabled(
          input::features::kUseAndroidBufferedInputDispatch);
  return should_request_unbuffered_dispatch;
}

bool IsDragAndDropEnabled() {
  // Cache the feature flag value so it isn't queried on every drag start.
  static const bool drag_feature_enabled =
      base::FeatureList::IsEnabled(features::kTouchDragAndContextMenu);
  return drag_feature_enabled;
}

bool IsDragEnabledForDropData(const DropData& drop_data) {
  return IsDragAndDropEnabled() || drop_data.text.has_value();
}
}  // namespace

// static
void SynchronousCompositor::SetClientForWebContents(
    WebContents* contents,
    SynchronousCompositorClient* client) {
  DCHECK(contents);
  DCHECK(client);
  WebContentsViewAndroid* wcva = static_cast<WebContentsViewAndroid*>(
      static_cast<WebContentsImpl*>(contents)->GetView());
  DCHECK(!wcva->synchronous_compositor_client());
  wcva->set_synchronous_compositor_client(client);
  RenderWidgetHostViewAndroid* rwhv = static_cast<RenderWidgetHostViewAndroid*>(
      contents->GetRenderWidgetHostView());
  if (rwhv) {
    rwhv->SetSynchronousCompositorClient(client);
  }
}

std::unique_ptr<WebContentsView> CreateWebContentsView(
    WebContentsImpl* web_contents,
    std::unique_ptr<WebContentsViewDelegate> delegate,
    raw_ptr<RenderViewHostDelegateView>* render_view_host_delegate_view) {
  auto rv = std::make_unique<WebContentsViewAndroid>(web_contents,
                                                     std::move(delegate));
  *render_view_host_delegate_view = rv.get();
  return rv;
}

// static
void WebContentsViewAndroid::InstallCreateHookForTests(
    RenderWidgetHostViewCreateFunction create_render_widget_host_view) {
  CHECK_EQ(nullptr, g_create_render_widget_host_view);
  g_create_render_widget_host_view = create_render_widget_host_view;
}

WebContentsViewAndroid::WebContentsViewAndroid(
    WebContentsImpl* web_contents,
    std::unique_ptr<WebContentsViewDelegate> delegate)
    : WebContentsObserver(web_contents),
      web_contents_(web_contents),
      delegate_(std::move(delegate)),
      view_(ui::ViewAndroid::LayoutType::kNormal),
      synchronous_compositor_client_(nullptr) {
  view_.SetLayer(cc::slim::Layer::Create());
  view_.set_event_handler(this);

  // `rwhva_parent_` is a child layer of `view_`.
  parent_for_web_page_widgets_ = cc::slim::Layer::Create();
  view_.GetLayer()->AddChild(parent_for_web_page_widgets_);
  if (GetContentClient()->browser()->ShouldAnimateBackForwardTransitions()) {
    back_forward_animation_manager_ =
        std::make_unique<BackForwardTransitionAnimationManagerAndroid>(
            this, &web_contents_->GetController());
  }
}

WebContentsViewAndroid::~WebContentsViewAndroid() {
  // The animation manager holds a reference to `parent_for_web_page_widgets_`.
  // Explicitly destroy the animation manager before resetting
  // `parent_for_web_page_widgets_`.
  if (back_forward_animation_manager_) {
    back_forward_animation_manager_.reset();
  }

  // Opposite to the construction order - disconnect the child first.
  parent_for_web_page_widgets_->RemoveFromParent();
  parent_for_web_page_widgets_.reset();

  if (view_.GetLayer()) {
    view_.GetLayer()->RemoveFromParent();
  }
  view_.set_event_handler(nullptr);
}

void WebContentsViewAndroid::SetContentUiEventHandler(
    std::unique_ptr<ContentUiEventHandler> handler) {
  content_ui_event_handler_ = std::move(handler);
}

void WebContentsViewAndroid::SetOverscrollRefreshHandler(
    std::unique_ptr<ui::OverscrollRefreshHandler> overscroll_refresh_handler) {
  auto* rwhv = web_contents_->GetRenderWidgetHostView();
  auto* rwhva =
      rwhv ? static_cast<RenderWidgetHostViewAndroid*>(rwhv) : nullptr;

  if (rwhva) {
    rwhva->ResetOverscrollController();
  }

  overscroll_refresh_handler_ = std::move(overscroll_refresh_handler);

  if (rwhva && overscroll_refresh_handler_) {
    rwhva->OnOverscrollRefreshHandlerAvailable();
  }
}

ui::OverscrollRefreshHandler*
WebContentsViewAndroid::GetOverscrollRefreshHandler() const {
  return overscroll_refresh_handler_.get();
}

gfx::NativeView WebContentsViewAndroid::GetNativeView() const {
  return const_cast<gfx::NativeView>(&view_);
}

gfx::NativeView WebContentsViewAndroid::GetContentNativeView() const {
  RenderWidgetHostView* rwhv = web_contents_->GetRenderWidgetHostView();
  if (rwhv) {
    return rwhv->GetNativeView();
  }

  // TODO(sievers): This should return null.
  return GetNativeView();
}

RenderWidgetHostViewAndroid*
WebContentsViewAndroid::GetRenderWidgetHostViewAndroid() {
  RenderWidgetHostView* rwhv = nullptr;
  rwhv = web_contents_->GetRenderWidgetHostView();
  return static_cast<RenderWidgetHostViewAndroid*>(rwhv);
}

gfx::NativeWindow WebContentsViewAndroid::GetTopLevelNativeWindow() const {
  return view_.GetWindowAndroid();
}

gfx::Rect WebContentsViewAndroid::GetContainerBounds() const {
  return GetViewBounds();
}

void WebContentsViewAndroid::SetPageTitle(const std::u16string& title) {
  // Do nothing.
}

void WebContentsViewAndroid::Focus() {
  auto* rwhv = web_contents_->GetRenderWidgetHostView();
  if (rwhv) {
    static_cast<RenderWidgetHostViewAndroid*>(rwhv)->Focus();
  }
}

void WebContentsViewAndroid::SetInitialFocus() {
  if (web_contents_->FocusLocationBarByDefault()) {
    web_contents_->SetFocusToLocationBar();
  } else {
    Focus();
  }
}

void WebContentsViewAndroid::StoreFocus() {
  NOTIMPLEMENTED();
}

void WebContentsViewAndroid::RestoreFocus() {
  NOTIMPLEMENTED();
}

void WebContentsViewAndroid::FocusThroughTabTraversal(bool reverse) {
  web_contents_->GetRenderViewHost()->SetInitialFocus(reverse);
}

DropData* WebContentsViewAndroid::GetDropData() const {
  return drop_data_.get();
}

gfx::Rect WebContentsViewAndroid::GetViewBounds() const {
  return gfx::Rect(view_.GetSizeDIPs());
}

void WebContentsViewAndroid::Resize(const gfx::Rect& new_bounds) {
  content::RenderWidgetHostView* view =
      web_contents_->GetRenderWidgetHostView();
  if (view) {
    view->SetBounds(new_bounds);
  }
}

gfx::Size WebContentsViewAndroid::GetSize() const {
  return view_.GetSizeDIPs();
}

void WebContentsViewAndroid::CreateView(gfx::NativeView context) {}

RenderWidgetHostViewBase* WebContentsViewAndroid::CreateViewForWidget(
    RenderWidgetHost* render_widget_host) {
  if (render_widget_host->GetView()) {
    // During testing, the view will already be set up in most cases to the
    // test view, so we don't want to clobber it with a real one. To verify that
    // this actually is happening (and somebody isn't accidentally creating the
    // view twice), we check for the RVH Factory, which will be set when we're
    // making special ones (which go along with the special views).
    DCHECK(RenderViewHostFactory::has_factory());
    return static_cast<RenderWidgetHostViewBase*>(
        render_widget_host->GetView());
  }
  // Note that while this instructs the render widget host to reference
  // |native_view_|, this has no effect without also instructing the
  // native view (i.e. ContentView) how to obtain a reference to this widget in
  // order to paint it.
  RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(render_widget_host);
  auto* rwhv = g_create_render_widget_host_view
                   ? g_create_render_widget_host_view(
                         rwhi, &view_, parent_for_web_page_widgets_.get())
                   : new RenderWidgetHostViewAndroid(
                         rwhi, &view_, parent_for_web_page_widgets_.get());
  rwhv->SetSynchronousCompositorClient(synchronous_compositor_client_);
  return rwhv;
}

RenderWidgetHostViewBase* WebContentsViewAndroid::CreateViewForChildWidget(
    RenderWidgetHost* render_widget_host) {
  RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(render_widget_host);
  return new RenderWidgetHostViewAndroid(rwhi, /*parent_native_view=*/nullptr,
                                         /*parent_layer=*/nullptr);
}

void WebContentsViewAndroid::RenderViewReady() {
  if (device_orientation_ == 0) {
    return;
  }
  auto* rwhva = GetRenderWidgetHostViewAndroid();
  if (rwhva) {
    rwhva->UpdateScreenInfo();
  }

  web_contents_->OnScreenOrientationChange();
}

void WebContentsViewAndroid::RenderViewHostChanged(RenderViewHost* old_host,
                                                   RenderViewHost* new_host) {
  if (old_host) {
    auto* rwhv = old_host->GetWidget()->GetView();
    if (rwhv && rwhv->GetNativeView()) {
      auto* rwhva = static_cast<RenderWidgetHostViewAndroid*>(rwhv);
      rwhva->ResetOverscrollController();
      rwhva->UpdateNativeViewTree(/*parent_native_view=*/nullptr,
                                  /*parent_layer=*/nullptr);
      rwhva->UpdateTooltip(std::u16string());
    }
  }

  auto* rwhv = new_host->GetWidget()->GetView();
  if (rwhv && rwhv->GetNativeView()) {
    auto* rwhva = static_cast<RenderWidgetHostViewAndroid*>(rwhv);
    rwhva->UpdateNativeViewTree(&view_, parent_for_web_page_widgets_.get());
    SetFocus(view_.HasFocus());
    rwhva->ResetOverscrollController();
    if (overscroll_refresh_handler_) {
      rwhva->OnOverscrollRefreshHandlerAvailable();
    }
  }
}

void WebContentsViewAndroid::SetFocus(bool focused) {
  auto* rwhva = GetRenderWidgetHostViewAndroid();
  if (!rwhva) {
    return;
  }
  if (focused) {
    rwhva->GotFocus();
  } else {
    rwhva->LostFocus();
  }
}

void WebContentsViewAndroid::SetOverscrollControllerEnabled(bool enabled) {}

void WebContentsViewAndroid::OnCapturerCountChanged() {}

void WebContentsViewAndroid::FullscreenStateChanged(bool is_fullscreen) {
  if (is_fullscreen && select_popup_) {
    select_popup_->HideMenu();
  }
}

BackForwardTransitionAnimationManager*
WebContentsViewAndroid::GetBackForwardTransitionAnimationManager() {
  if (!back_forward_animation_manager_ &&
      GetContentClient()->browser()->ShouldAnimateBackForwardTransitions()) {
    back_forward_animation_manager_ =
        std::make_unique<BackForwardTransitionAnimationManagerAndroid>(
            this, &web_contents_->GetController());
  }
  return back_forward_animation_manager_.get();
}

void WebContentsViewAndroid::DestroyBackForwardTransitionAnimationManager() {
  back_forward_animation_manager_.reset();
}

void WebContentsViewAndroid::ReadyToCommitNavigation(
    NavigationHandle* navigation_handle) {
  // Do nothing if the BlurTransitionAnimationManager is already instantiated.
  if (BlurTransitionAnimationManager::FromWebContents(web_contents_)) {
    return;
  }

  // Lazily instantiate the BlurTransitionAnimationManager.
  if (ShouldShowBlurTransitionAnimation(navigation_handle)) {
    BlurTransitionAnimationManager::CreateForWebContents(web_contents_);
    // In this lazy instantiation flow, the ReadyToCommitNavigation notification
    // was not received by the animation manager. We manually invoke to
    // ensure we don't miss the animation.
    BlurTransitionAnimationManager::FromWebContents(web_contents_)
        ->ReadyToCommitNavigation(navigation_handle);
  }
}

void WebContentsViewAndroid::ShowContextMenu(RenderFrameHost& render_frame_host,
                                             const ContextMenuParams& params) {
  if (is_active_drag_ && drag_exceeded_movement_threshold_) {
    return;
  }

  auto* rwhv = static_cast<RenderWidgetHostViewAndroid*>(
      web_contents_->GetRenderWidgetHostView());

  // See if context menu is handled by SelectionController as a selection menu.
  // If not, use the delegate to show it.
  if (rwhv && rwhv->ShowSelectionMenu(&render_frame_host, params)) {
    return;
  }

  if (delegate_) {
    delegate_->ShowContextMenu(render_frame_host, params);
  }
}

SelectPopup* WebContentsViewAndroid::GetSelectPopup() {
  if (!select_popup_) {
    select_popup_ = std::make_unique<SelectPopup>(web_contents_);
  }
  return select_popup_.get();
}

SelectionPopupController*
WebContentsViewAndroid::GetSelectionPopupController() {
  SelectionPopupController* controller = nullptr;
  if (auto* rwhva = GetRenderWidgetHostViewAndroid()) {
    controller = rwhva->selection_popup_controller();
  }
  return controller;
}

void WebContentsViewAndroid::ShowPopupMenu(
    RenderFrameHost* render_frame_host,
    mojo::PendingRemote<blink::mojom::PopupMenuClient> popup_client,
    const gfx::Rect& bounds,
    double item_font_size,
    int selected_item,
    std::vector<blink::mojom::MenuItemPtr> menu_items,
    bool right_aligned,
    bool allow_multiple_selection) {
  GetSelectPopup()->ShowMenu(std::move(popup_client), bounds, item_font_size,
                             std::move(menu_items), selected_item,
                             allow_multiple_selection, right_aligned);
}

void WebContentsViewAndroid::StartDragging(
    RenderFrameHost& source_rfh,
    const DropData& drop_data,
    blink::DragOperationsMask allowed_ops,
    const gfx::ImageSkia& image,
    const gfx::Vector2d& cursor_offset,
    const gfx::Rect& drag_obj_rect,
    const blink::mojom::DragEventSourceInfo& event_info) {
  // Disallow reentrant drag which could be an attempt to exploit drag state.
  if (drag_security_info_.did_initiate()) {
    return;
  }
  RenderWidgetHostImpl* const source_rwh =
      static_cast<RenderWidgetHostImpl*>(source_rfh.GetRenderWidgetHost());
  current_source_rwh_for_drag_ = source_rwh->GetWeakPtr();
  if (!IsDragEnabledForDropData(drop_data)) {
    ClearDragStateOnStartFailure(source_rwh);
    return;
  }

  gfx::NativeView native_view = GetNativeView();
  if (!native_view) {
    ClearDragStateOnStartFailure(source_rwh);
    return;
  }

  ClipboardEndpoint source_endpoint = CreateClipboardEndpoint(source_rfh);

  if (!IsDragAllowedByDataControlPolicy(source_endpoint, drop_data)) {
    ClearDragStateOnStartFailure(source_rwh);
    return;
  }
  drag_security_info_.OnDragInitiated(source_rwh, drop_data);

  const SkBitmap* bitmap = image.bitmap();
  SkBitmap dummy_bitmap;

  if (image.size().IsEmpty()) {
    // An empty drag image is possible if the Javascript sets an empty drag
    // image on purpose.
    // Create a dummy 1x1 pixel image to avoid crashes when converting to java
    // bitmap.
    dummy_bitmap.allocN32Pixels(1, 1);
    dummy_bitmap.eraseColor(0);
    bitmap = &dummy_bitmap;
  }

  // TODO(crbug.com/40886472): Consolidate cursor_offset and drag_obj_rect with
  // drop_data.

  ScopedJavaLocalRef<jobject> jdrop_data = ToJavaDropData(drop_data);
  if (!native_view->StartDragAndDrop(
          gfx::ConvertToJavaBitmap(*bitmap), jdrop_data, cursor_offset.x(),
          cursor_offset.y(), drag_obj_rect.width(), drag_obj_rect.height())) {
    ClearDragStateOnStartFailure(source_rwh);
    return;
  }

  if (auto* selection_popup_controller = GetSelectionPopupController()) {
    selection_popup_controller->HidePopupsAndPreserveSelection();
    // Hide the handles temporarily.
    auto* rwhva = GetRenderWidgetHostViewAndroid();
    if (rwhva) {
      rwhva->SetTextHandlesTemporarilyHidden(true);
    }
  }
}

void WebContentsViewAndroid::UpdateDragOperation(
    ui::mojom::DragOperation op,
    bool document_is_handling_drag) {
  // Intentional not storing `op` because Android does not support drag and
  // drop cursor yet.
  document_is_handling_drag_ = document_is_handling_drag;
}

// Pass events to the renderer. In order to support OOPIF, we need to call
// WebContents::GetRenderWidgetHostAtPointAsynchronously() with the location of
// the event to determine which process to send the event to. This function
// seems to always return synchronously in this context, but has the potential
// to be async if there are pending events queued.
// GetRenderWidgetHostAtPointAsynchronously() is called for DRAG_LOCATION
// and DROP, but not for DRAG_ENTERED, DRAG_EXITED, or DRAG_ENDED since they do
// not contain a location. This creates a potential for events to arrive out of
// order, but testing with blink shows that it handles this ok.
//
// As the mouse moves across a page, if we detect that the RenderWidgetHost
// changes, we resend the entered event before sending the update or drop.
bool WebContentsViewAndroid::OnDragEvent(const ui::DragEventAndroid& event) {
  switch (event.action()) {
    case DragEventJni::ACTION_DRAG_ENTERED: {
      drag_metadata_.clear();
      for (const std::u16string& mime_type : event.mime_types()) {
        if (mime_type == ui::kMimeTypePlainText16 ||
            mime_type == ui::kMimeTypeHtml16 ||
            mime_type == ui::kMimeTypeMozillaUrl16 ||
            mime_type == kMimeTypeIntent || mime_type == kMimeTypeChromeLink) {
          drag_metadata_.push_back(DropData::Metadata::CreateForMimeType(
              DropData::Kind::STRING, mime_type));
        } else {
          // Create a file extension from the mime type.
          std::string ext = base::UTF16ToUTF8(mime_type);
          if (!net::GetPreferredExtensionForMimeType(ext, &ext)) {
            // Use mime subtype as a fallback.
            net::ParseMimeTypeWithoutParameter(ext, nullptr, &ext);
          }
          drag_metadata_.push_back(DropData::Metadata::CreateForFilePath(
              base::FilePath("file." + ext)));
        }
      }
      OnDragEntered(event.location(), event.screen_location());
      break;
    }
    case DragEventJni::ACTION_DRAG_LOCATION:
      OnDragUpdated(event.location(), event.screen_location());
      break;
    case DragEventJni::ACTION_DROP: {
      drop_data_ = std::make_unique<DropData>();
      drop_data_->did_originate_from_renderer = false;
      drop_data_->document_is_handling_drag = document_is_handling_drag_;
      PopulateDropDataFromEvent(event, drop_data_.get());

      OnPerformDrop(event.location(), event.screen_location());
      break;
    }
    case DragEventJni::ACTION_DRAG_EXITED:
      OnDragExited();
      break;
    case DragEventJni::ACTION_DRAG_ENDED:
      OnDragEnded();
      break;
    case DragEventJni::ACTION_DRAG_STARTED:
      // Nothing meaningful to do.
      break;
  }
  return true;
}

void WebContentsViewAndroid::OnDragEntered(const gfx::PointF& location,
                                           const gfx::PointF& screen_location) {
  // Android does not pass a valid location for ACTION_DRAG_STARTED, so do not
  // try to find GetRenderWidgetHostAtPointAsynchronously().
  auto* rwhv = web_contents_->GetRenderWidgetHostView();
  if (rwhv) {
    DragEnteredCallback(
        location, screen_location,
        static_cast<RenderWidgetHostViewBase*>(rwhv)->GetWeakPtr());
  }
}

void WebContentsViewAndroid::DragEnteredCallback(
    const gfx::PointF& location,
    const gfx::PointF& screen_location,
    base::WeakPtr<RenderWidgetHostViewBase> target) {
  if (!target) {
    return;
  }

  RenderWidgetHostImpl* target_rwh =
      RenderWidgetHostImpl::From(target->GetRenderWidgetHost());
  if (!drag_security_info_.IsValidDragTarget(target_rwh)) {
    return;
  }

  current_target_rwh_for_drag_ = target_rwh->GetWeakPtr();

  blink::DragOperationsMask allowed_ops =
      static_cast<blink::DragOperationsMask>(blink::kDragOperationCopy |
                                             blink::kDragOperationMove);
  current_target_rwh_for_drag_->DragTargetDragEnterWithMetaData(
      drag_metadata_, location, screen_location, allowed_ops, 0,
      base::DoNothing());
}

void WebContentsViewAndroid::OnDragUpdated(const gfx::PointF& location,
                                           const gfx::PointF& screen_location) {
  drag_location_ = location;
  drag_screen_location_ = screen_location;

  // When drag and drop is enabled, attempt to dismiss the context menu if drag
  // leaves start location.
  if (IsDragAndDropEnabled()) {
    // On Android DragEvent.ACTION_DRAG_ENTER does not have a valid location.
    // See
    // https://developer.android.com/develop/ui/views/touch-and-input/drag-drop/concepts#table2.
    if (!is_active_drag_) {
      is_active_drag_ = true;
      drag_entered_location_ = location;
    } else if (!drag_exceeded_movement_threshold_) {
      int radius = DragMovementThresholdDip();
      if (!drag_location_.IsWithinDistance(drag_entered_location_, radius)) {
        drag_exceeded_movement_threshold_ = true;
        if (delegate_) {
          delegate_->DismissContextMenu();
        }
      }
    }
  }

  auto* rwhv = web_contents_->GetRenderWidgetHostView();
  if (rwhv) {
    web_contents_->GetRenderWidgetHostAtPointAsynchronously(
        static_cast<RenderWidgetHostViewBase*>(rwhv), location,
        base::BindOnce(&WebContentsViewAndroid::DragUpdatedCallback,
                       weak_ptr_factory_.GetWeakPtr(), location,
                       screen_location));
  }
}

void WebContentsViewAndroid::DragUpdatedCallback(
    const gfx::PointF& location,
    const gfx::PointF& screen_location,
    base::WeakPtr<RenderWidgetHostViewBase> target,
    std::optional<gfx::PointF> transformed_pt) {
  if (!target) {
    return;
  }
  RenderWidgetHostImpl* target_rwh =
      RenderWidgetHostImpl::From(target->GetRenderWidgetHost());
  if (!drag_security_info_.IsValidDragTarget(target_rwh)) {
    return;
  }

  if (target_rwh != current_target_rwh_for_drag_.get()) {
    if (current_target_rwh_for_drag_) {
      gfx::PointF transformed_leave_point = location;
      auto* rwhv = web_contents_->GetRenderWidgetHostView();
      if (!rwhv) {
        return;
      }
      static_cast<RenderWidgetHostViewBase*>(rwhv)
          ->TransformPointToCoordSpaceForView(
              location,
              static_cast<RenderWidgetHostViewBase*>(
                  current_target_rwh_for_drag_->GetView()),
              &transformed_leave_point);
      current_target_rwh_for_drag_->DragTargetDragLeave(transformed_leave_point,
                                                        screen_location);
    }
    DragEnteredCallback(location, screen_location, target);
  }

  blink::DragOperationsMask allowed_ops =
      static_cast<blink::DragOperationsMask>(blink::kDragOperationCopy |
                                             blink::kDragOperationMove);
  target_rwh->DragTargetDragOver(transformed_pt.value(), drag_screen_location_,
                                 allowed_ops, 0, base::DoNothing());
}

void WebContentsViewAndroid::OnDragExited() {
  if (current_target_rwh_for_drag_) {
    current_target_rwh_for_drag_->DragTargetDragLeave(gfx::PointF(),
                                                      gfx::PointF());
  }
}

void WebContentsViewAndroid::OnPerformDrop(const gfx::PointF& location,
                                           const gfx::PointF& screen_location) {
  auto* rwhv = web_contents_->GetRenderWidgetHostView();
  if (rwhv) {
    web_contents_->GetRenderWidgetHostAtPointAsynchronously(
        static_cast<RenderWidgetHostViewBase*>(rwhv), location,
        base::BindOnce(&WebContentsViewAndroid::PerformDropCallback,
                       weak_ptr_factory_.GetWeakPtr(), location,
                       screen_location));
  }
}

void WebContentsViewAndroid::PerformDropCallback(
    const gfx::PointF& location,
    const gfx::PointF& screen_location,
    base::WeakPtr<RenderWidgetHostViewBase> target,
    std::optional<gfx::PointF> transformed_pt) {
  if (!target) {
    return;
  }
  RenderWidgetHostImpl* target_rwh =
      RenderWidgetHostImpl::From(target->GetRenderWidgetHost());
  if (!drag_security_info_.IsValidDragTarget(target_rwh)) {
    return;
  }

  if (target_rwh != current_target_rwh_for_drag_.get()) {
    if (current_target_rwh_for_drag_) {
      current_target_rwh_for_drag_->DragTargetDragLeave(*transformed_pt,
                                                        screen_location);
    }
    DragEnteredCallback(location, screen_location, target);
  }

  web_contents_->Focus();
  target_rwh->FilterDropData(drop_data_.get());
  target_rwh->DragTargetDrop(*drop_data_, *transformed_pt, screen_location, 0,
                             base::DoNothing());
}

void WebContentsViewAndroid::OnSystemDragEnded(RenderWidgetHost* source_rwh) {
  web_contents_->SystemDragEnded(source_rwh);

  // Restore the selection popups and the text handles if necessary.
  if (auto* selection_popup_controller = GetSelectionPopupController()) {
    selection_popup_controller->RestoreSelectionPopupsIfNecessary();
    auto* rwhva = GetRenderWidgetHostViewAndroid();
    if (rwhva) {
      rwhva->SetTextHandlesTemporarilyHidden(false);
    }
  }
}

void WebContentsViewAndroid::ClearDragStateOnStartFailure(
    RenderWidgetHost* source_rwh) {
  // Notify Blink to end the drag session. Since the system/OS drag was never
  // actually initiated, this resets renderer state without sending any DOM
  // events.
  OnSystemDragEnded(source_rwh);
  current_source_rwh_for_drag_.reset();
  drag_security_info_.OnDragEnded();
}

void WebContentsViewAndroid::OnDragEnded() {
  if (current_source_rwh_for_drag_) {
    web_contents_->DragSourceEndedAt(
        drag_location_.x(), drag_location_.y(), drag_screen_location_.x(),
        drag_screen_location_.y(), ui::mojom::DragOperation::kNone,
        current_source_rwh_for_drag_.get());
    OnSystemDragEnded(current_source_rwh_for_drag_.get());
  }
  drag_security_info_.OnDragEnded();

  drag_metadata_.clear();
  current_source_rwh_for_drag_.reset();
  current_target_rwh_for_drag_.reset();
  is_active_drag_ = false;
  drag_exceeded_movement_threshold_ = false;
  drag_entered_location_ = gfx::PointF();
  drag_location_ = gfx::PointF();
  drag_screen_location_ = gfx::PointF();
}

void WebContentsViewAndroid::GotFocus(
    RenderWidgetHostImpl* render_widget_host) {
  web_contents_->NotifyWebContentsFocused(render_widget_host);
}

void WebContentsViewAndroid::LostFocus(
    RenderWidgetHostImpl* render_widget_host) {
  web_contents_->NotifyWebContentsLostFocus(render_widget_host);
}

// This is called when we the renderer asks us to take focus back (i.e., it has
// iterated past the last focusable element on the page).
void WebContentsViewAndroid::TakeFocus(bool reverse) {
  if (web_contents_->GetDelegate() &&
      web_contents_->GetDelegate()->TakeFocus(web_contents_, reverse)) {
    return;
  }
  auto* rwhv = web_contents_->GetRenderWidgetHostView();
  if (rwhv) {
    rwhv->Focus();
  }
}

int WebContentsViewAndroid::GetTopControlsHeight() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate ? delegate->GetTopControlsHeight() : 0;
}

int WebContentsViewAndroid::GetTopControlsMinHeight() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate ? delegate->GetTopControlsMinHeight() : 0;
}

int WebContentsViewAndroid::GetBottomControlsHeight() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate ? delegate->GetBottomControlsHeight() : 0;
}

int WebContentsViewAndroid::GetBottomControlsMinHeight() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate ? delegate->GetBottomControlsMinHeight() : 0;
}

bool WebContentsViewAndroid::ShouldAnimateBrowserControlsHeightChanges() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate && delegate->ShouldAnimateBrowserControlsHeightChanges();
}

bool WebContentsViewAndroid::DoBrowserControlsShrinkRendererSize() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate &&
         delegate->DoBrowserControlsShrinkRendererSize(web_contents_);
}

bool WebContentsViewAndroid::OnlyExpandTopControlsAtPageTop() const {
  auto* delegate = web_contents_->GetDelegate();
  return delegate && delegate->OnlyExpandTopControlsAtPageTop();
}

bool WebContentsViewAndroid::OnTouchEvent(const ui::MotionEventAndroid& event) {
  if (event.GetAction() == ui::MotionEventAndroid::Action::DOWN &&
      ShouldRequestUnbufferedDispatch()) {
    view_.RequestUnbufferedDispatch(event);
  }
  return false;  // let the children handle the actual event.
}

bool WebContentsViewAndroid::OnMouseEvent(const ui::MotionEventAndroid& event) {
  // Hover events can be intercepted when in accessibility mode.
  auto action = event.GetAction();
  if (action != ui::MotionEventAndroid::Action::HOVER_ENTER &&
      action != ui::MotionEventAndroid::Action::HOVER_EXIT &&
      action != ui::MotionEventAndroid::Action::HOVER_MOVE) {
    return false;
  }

  auto* manager = static_cast<BrowserAccessibilityManagerAndroid*>(
      web_contents_->GetRootBrowserAccessibilityManager());
  return manager && manager->OnHoverEvent(event);
}

bool WebContentsViewAndroid::OnGenericMotionEvent(
    const ui::MotionEventAndroid& event) {
  if (content_ui_event_handler_) {
    return content_ui_event_handler_->OnGenericMotionEvent(event);
  }
  return false;
}

bool WebContentsViewAndroid::OnKeyUp(const ui::KeyEventAndroid& event) {
  if (content_ui_event_handler_) {
    return content_ui_event_handler_->OnKeyUp(event);
  }
  return false;
}

bool WebContentsViewAndroid::DispatchKeyEvent(
    const ui::KeyEventAndroid& event) {
  if (content_ui_event_handler_) {
    return content_ui_event_handler_->DispatchKeyEvent(event);
  }
  return false;
}

bool WebContentsViewAndroid::ScrollBy(float delta_x, float delta_y) {
  if (content_ui_event_handler_) {
    content_ui_event_handler_->ScrollBy(delta_x, delta_y);
  }
  return false;
}

bool WebContentsViewAndroid::ScrollTo(float x, float y) {
  if (content_ui_event_handler_) {
    content_ui_event_handler_->ScrollTo(x, y);
  }
  return false;
}

void WebContentsViewAndroid::OnSizeChanged() {
  auto* rwhv = GetRenderWidgetHostViewAndroid();
  if (rwhv) {
    web_contents_->SendScreenRects();
    auto deadline_policy = cc::DeadlinePolicy::UseSpecifiedDeadline(
        ui::DelegatedFrameHostAndroid::ResizeTimeoutFrames());
    rwhv->SynchronizeVisualProperties(deadline_policy, std::nullopt);
  }
}

void WebContentsViewAndroid::OnPhysicalBackingSizeChanged(
    std::optional<base::TimeDelta> deadline_override) {
  if (back_forward_animation_manager_) {
    back_forward_animation_manager_->OnPhysicalBackingSizeChanged();
  }
  if (web_contents_->GetRenderWidgetHostView()) {
    web_contents_->SendScreenRects();
  }
}

void WebContentsViewAndroid::OnBrowserControlsHeightChanged() {
  auto* rwhv = GetRenderWidgetHostViewAndroid();
  if (rwhv) {
    rwhv->SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(),
                                      std::nullopt);
  }
}

void WebContentsViewAndroid::OnControlsResizeViewChanged() {
  auto* rwhv = GetRenderWidgetHostViewAndroid();
  if (rwhv) {
    rwhv->SynchronizeVisualProperties(cc::DeadlinePolicy::UseDefaultDeadline(),
                                      std::nullopt);
  }
}

void WebContentsViewAndroid::NotifyVirtualKeyboardOverlayRect(
    const gfx::Rect& keyboard_rect) {
  auto* rwhv = GetRenderWidgetHostViewAndroid();
  if (rwhv) {
    rwhv->NotifyVirtualKeyboardOverlayRect(keyboard_rect);
  }
}

void WebContentsViewAndroid::ShowInterestInElement(int nodeID) {
  auto* rwhv = GetRenderWidgetHostViewAndroid();
  if (rwhv) {
    rwhv->ShowInterestInElement(nodeID);
  }
}

bool WebContentsViewAndroid::ShouldShowBlurTransitionAnimation(
    NavigationHandle* navigation_handle) {
  if (delegate_) {
    return delegate_->ShouldShowBlurTransitionAnimation(navigation_handle);
  }
  return false;
}

bool WebContentsViewAndroid::IsDragAllowedByDataControlPolicy(
    const ClipboardEndpoint& source,
    const DropData& drop_data) {
  return GetContentClient()->browser()->IsDragAllowedByPolicy(source,
                                                              drop_data);
}

}  // namespace content
