// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/android/view_android.h" #include #include #include #include "base/android/jni_android.h" #include "base/android/jni_string.h" #include "base/containers/adapters.h" #include "base/containers/contains.h" #include "cc/layers/layer.h" #include "components/viz/common/frame_sinks/copy_output_request.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/android/event_forwarder.h" #include "ui/android/ui_android_jni_headers/ViewAndroidDelegate_jni.h" #include "ui/android/window_android.h" #include "ui/base/cursor/cursor.h" #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h" #include "ui/base/layout.h" #include "ui/events/android/drag_event_android.h" #include "ui/events/android/event_handler_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/geometry/point.h" #include "url/gurl.h" namespace ui { using base::android::ConvertUTF8ToJavaString; using base::android::JavaRef; using base::android::ScopedJavaLocalRef; ViewAndroid::ScopedAnchorView::ScopedAnchorView( JNIEnv* env, const JavaRef& jview, const JavaRef& jdelegate) : view_(env, jview.obj()), delegate_(env, jdelegate.obj()) { // If there's a view, then we need a delegate to remove it. DCHECK(!jdelegate.is_null() || jview.is_null()); } ViewAndroid::ScopedAnchorView::ScopedAnchorView() { } ViewAndroid::ScopedAnchorView::ScopedAnchorView(ScopedAnchorView&& other) { view_ = other.view_; other.view_.reset(); delegate_ = other.delegate_; other.delegate_.reset(); } ViewAndroid::ScopedAnchorView& ViewAndroid::ScopedAnchorView::operator=(ScopedAnchorView&& other) { if (this != &other) { view_ = other.view_; other.view_.reset(); delegate_ = other.delegate_; other.delegate_.reset(); } return *this; } ViewAndroid::ScopedAnchorView::~ScopedAnchorView() { Reset(); } void ViewAndroid::ScopedAnchorView::Reset() { JNIEnv* env = base::android::AttachCurrentThread(); const ScopedJavaLocalRef view = view_.get(env); const ScopedJavaLocalRef delegate = delegate_.get(env); if (!view.is_null() && !delegate.is_null()) { Java_ViewAndroidDelegate_removeView(env, delegate, view); } view_.reset(); delegate_.reset(); } const base::android::ScopedJavaLocalRef ViewAndroid::ScopedAnchorView::view() const { JNIEnv* env = base::android::AttachCurrentThread(); return view_.get(env); } ViewAndroid::ViewAndroid(LayoutType layout_type) : parent_(nullptr), layout_type_(layout_type) {} ViewAndroid::ViewAndroid() : ViewAndroid(LayoutType::NORMAL) {} ViewAndroid::~ViewAndroid() { RemoveAllChildren(GetWindowAndroid() != nullptr); for (auto& observer : observer_list_) observer.OnViewAndroidDestroyed(); observer_list_.Clear(); RemoveFromParent(); } void ViewAndroid::SetDelegate(const JavaRef& delegate) { // A ViewAndroid may have its own delegate or otherwise will use the next // available parent's delegate. JNIEnv* env = base::android::AttachCurrentThread(); delegate_ = JavaObjectWeakGlobalRef(env, delegate); } void ViewAndroid::UpdateFrameInfo(const FrameInfo& frame_info) { frame_info_ = frame_info; } float ViewAndroid::GetDipScale() { return ui::GetScaleFactorForNativeView(this); } absl::optional ViewAndroid::GetDisplayFeature() { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return absl::nullopt; JNIEnv* env = base::android::AttachCurrentThread(); base::android::ScopedJavaLocalRef jni_display_feature = Java_ViewAndroidDelegate_getDisplayFeature(env, delegate); std::vector display_feature_values; if (jni_display_feature.obj()) { // In order to reduce jni overhead, the DisplayFeature is returned in // an integer array. This array must have 4 items in it (or the return // value should be null). base::android::JavaIntArrayToIntVector(env, jni_display_feature, &display_feature_values); CHECK(display_feature_values.size() == 4); gfx::Rect display_feature; display_feature.SetByBounds( display_feature_values[0], display_feature_values[1], display_feature_values[2], display_feature_values[3]); return display_feature; } return absl::nullopt; } ScopedJavaLocalRef ViewAndroid::GetEventForwarder() { if (!event_forwarder_) { DCHECK(!RootPathHasEventForwarder(parent_)) << "The view tree path already has an event forwarder."; DCHECK(!SubtreeHasEventForwarder(this)) << "The view tree path already has an event forwarder."; event_forwarder_.reset(new EventForwarder(this)); } return event_forwarder_->GetJavaObject(); } void ViewAndroid::AddChild(ViewAndroid* child) { DCHECK(child); DCHECK(!base::Contains(children_, child)); DCHECK(!RootPathHasEventForwarder(this) || !SubtreeHasEventForwarder(child)) << "Some view tree path will have more than one event forwarder " "if the child is added."; // The new child goes to the top, which is the end of the list. children_.push_back(child); if (child->parent_) child->RemoveFromParent(); child->parent_ = this; // Empty physical backing size need not propagating down since it can // accidentally overwrite the valid ones in the children. if (!physical_size_.IsEmpty()) child->OnPhysicalBackingSizeChanged(physical_size_); child->OnControlsResizeViewChanged(controls_resize_view_); // Empty view size also need not propagating down in order to prevent // spurious events with empty size from being sent down. if (child->match_parent() && !bounds_.IsEmpty() && child->GetSize() != bounds_.size()) { child->OnSizeChangedInternal(bounds_.size()); child->DispatchOnSizeChanged(); } if (GetWindowAndroid()) child->OnAttachedToWindow(); } // static bool ViewAndroid::RootPathHasEventForwarder(ViewAndroid* view) { while (view) { if (view->has_event_forwarder()) return true; view = view->parent_; } return false; } // static bool ViewAndroid::SubtreeHasEventForwarder(ViewAndroid* view) { if (view->has_event_forwarder()) return true; for (auto* child : view->children_) { if (SubtreeHasEventForwarder(child)) return true; } return false; } void ViewAndroid::MoveToFront(ViewAndroid* child) { DCHECK(child); auto it = std::find(children_.begin(), children_.end(), child); DCHECK(it != children_.end()); // Top element is placed at the end of the list. if (*it != children_.back()) children_.splice(children_.end(), children_, it); } void ViewAndroid::MoveToBack(ViewAndroid* child) { DCHECK(child); auto it = std::find(children_.begin(), children_.end(), child); DCHECK(it != children_.end()); // Bottom element is placed at the beginning of the list. if (*it != children_.front()) children_.splice(children_.begin(), children_, it); } void ViewAndroid::RemoveFromParent() { if (parent_) parent_->RemoveChild(this); } ViewAndroid::ScopedAnchorView ViewAndroid::AcquireAnchorView() { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return ViewAndroid::ScopedAnchorView(); JNIEnv* env = base::android::AttachCurrentThread(); return ViewAndroid::ScopedAnchorView( env, Java_ViewAndroidDelegate_acquireView(env, delegate), delegate); } void ViewAndroid::SetAnchorRect(const JavaRef& anchor, const gfx::RectF& bounds_dip) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return; float dip_scale = GetDipScale(); int left_margin = std::round(bounds_dip.x() * dip_scale); // Note that content_offset() is in CSS scale and bounds_dip is in DIP scale // (i.e., CSS pixels * page scale factor), but the height of browser control // is not affected by page scale factor. Thus, content_offset() in CSS scale // is also in DIP scale. int top_margin = std::round((content_offset() + bounds_dip.y()) * dip_scale); const gfx::RectF bounds_px = gfx::ScaleRect(bounds_dip, dip_scale); JNIEnv* env = base::android::AttachCurrentThread(); Java_ViewAndroidDelegate_setViewPosition( env, delegate, anchor, bounds_px.x(), bounds_px.y(), bounds_px.width(), bounds_px.height(), left_margin, top_margin); } ScopedJavaLocalRef ViewAndroid::GetContainerView() { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return nullptr; JNIEnv* env = base::android::AttachCurrentThread(); return Java_ViewAndroidDelegate_getContainerView(env, delegate); } gfx::Point ViewAndroid::GetLocationOfContainerViewInWindow() { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return gfx::Point(); JNIEnv* env = base::android::AttachCurrentThread(); gfx::Point result( Java_ViewAndroidDelegate_getXLocationOfContainerViewInWindow(env, delegate), Java_ViewAndroidDelegate_getYLocationOfContainerViewInWindow(env, delegate)); return result; } gfx::PointF ViewAndroid::GetLocationOnScreen(float x, float y) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return gfx::PointF(); JNIEnv* env = base::android::AttachCurrentThread(); float loc_x = Java_ViewAndroidDelegate_getXLocationOnScreen(env, delegate); float loc_y = Java_ViewAndroidDelegate_getYLocationOnScreen(env, delegate); return gfx::PointF(x + loc_x, y + loc_y); } void ViewAndroid::RemoveAllChildren(bool attached_to_window) { auto it = children_.begin(); while (it != children_.end()) { if (attached_to_window) (*it)->OnDetachedFromWindow(); (*it)->parent_ = nullptr; // erase returns a new iterator for the element following the ereased one. it = children_.erase(it); } } void ViewAndroid::RemoveChild(ViewAndroid* child) { DCHECK(child); DCHECK_EQ(child->parent_, this); if (GetWindowAndroid()) child->OnDetachedFromWindow(); std::list::iterator it = std::find(children_.begin(), children_.end(), child); DCHECK(it != children_.end()); children_.erase(it); child->parent_ = nullptr; } void ViewAndroid::AddObserver(ViewAndroidObserver* observer) { observer_list_.AddObserver(observer); } void ViewAndroid::RemoveObserver(ViewAndroidObserver* observer) { observer_list_.RemoveObserver(observer); } void ViewAndroid::RequestDisallowInterceptTouchEvent() { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return; JNIEnv* env = base::android::AttachCurrentThread(); Java_ViewAndroidDelegate_requestDisallowInterceptTouchEvent(env, delegate); } void ViewAndroid::RequestUnbufferedDispatch(const MotionEventAndroid& event) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return; JNIEnv* env = base::android::AttachCurrentThread(); Java_ViewAndroidDelegate_requestUnbufferedDispatch(env, delegate, event.GetJavaObject()); } void ViewAndroid::SetCopyOutputCallback(CopyViewCallback callback) { copy_view_callback_ = std::move(callback); } // If view does not support copy request, return back the request. std::unique_ptr ViewAndroid::MaybeRequestCopyOfView( std::unique_ptr request) { if (copy_view_callback_.is_null()) return request; copy_view_callback_.Run(std::move(request)); return nullptr; } void ViewAndroid::OnAttachedToWindow() { for (auto& observer : observer_list_) observer.OnAttachedToWindow(); for (auto* child : children_) child->OnAttachedToWindow(); } void ViewAndroid::OnDetachedFromWindow() { for (auto& observer : observer_list_) observer.OnDetachedFromWindow(); for (auto* child : children_) child->OnDetachedFromWindow(); } WindowAndroid* ViewAndroid::GetWindowAndroid() const { return parent_ ? parent_->GetWindowAndroid() : nullptr; } const ScopedJavaLocalRef ViewAndroid::GetViewAndroidDelegate() const { JNIEnv* env = base::android::AttachCurrentThread(); const ScopedJavaLocalRef delegate = delegate_.get(env); if (!delegate.is_null()) return delegate; return parent_ ? parent_->GetViewAndroidDelegate() : delegate; } cc::Layer* ViewAndroid::GetLayer() const { return layer_.get(); } bool ViewAndroid::HasFocus() { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return false; JNIEnv* env = base::android::AttachCurrentThread(); return Java_ViewAndroidDelegate_hasFocus(env, delegate); } void ViewAndroid::RequestFocus() { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return; JNIEnv* env = base::android::AttachCurrentThread(); Java_ViewAndroidDelegate_requestFocus(env, delegate); } void ViewAndroid::SetLayer(scoped_refptr layer) { layer_ = layer; } bool ViewAndroid::StartDragAndDrop(const JavaRef& jshadow_image, const JavaRef& jdrop_data) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return false; JNIEnv* env = base::android::AttachCurrentThread(); return Java_ViewAndroidDelegate_startDragAndDrop(env, delegate, jshadow_image, jdrop_data); } void ViewAndroid::OnCursorChanged(const Cursor& cursor) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return; JNIEnv* env = base::android::AttachCurrentThread(); if (cursor.type() == mojom::CursorType::kCustom) { const SkBitmap& bitmap = cursor.custom_bitmap(); const gfx::Point& hotspot = cursor.custom_hotspot(); if (bitmap.drawsNothing()) { Java_ViewAndroidDelegate_onCursorChanged( env, delegate, static_cast(mojom::CursorType::kPointer)); return; } ScopedJavaLocalRef java_bitmap = gfx::ConvertToJavaBitmap(bitmap); Java_ViewAndroidDelegate_onCursorChangedToCustom(env, delegate, java_bitmap, hotspot.x(), hotspot.y()); } else { Java_ViewAndroidDelegate_onCursorChanged(env, delegate, static_cast(cursor.type())); } } void ViewAndroid::SetHoverActionStylusWritable(bool stylus_writable) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return; JNIEnv* env = base::android::AttachCurrentThread(); Java_ViewAndroidDelegate_setHoverActionStylusWritable(env, delegate, stylus_writable); } void ViewAndroid::OnBackgroundColorChanged(unsigned int color) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return; JNIEnv* env = base::android::AttachCurrentThread(); Java_ViewAndroidDelegate_onBackgroundColorChanged(env, delegate, color); } void ViewAndroid::OnTopControlsChanged(float top_controls_offset, float top_content_offset, float top_controls_min_height_offset) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return; JNIEnv* env = base::android::AttachCurrentThread(); Java_ViewAndroidDelegate_onTopControlsChanged( env, delegate, std::round(top_controls_offset), std::round(top_content_offset), std::round(top_controls_min_height_offset)); } void ViewAndroid::OnBottomControlsChanged( float bottom_controls_offset, float bottom_controls_min_height_offset) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return; JNIEnv* env = base::android::AttachCurrentThread(); Java_ViewAndroidDelegate_onBottomControlsChanged( env, delegate, std::round(bottom_controls_offset), std::round(bottom_controls_min_height_offset)); } int ViewAndroid::GetViewportInsetBottom() { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return 0; JNIEnv* env = base::android::AttachCurrentThread(); return Java_ViewAndroidDelegate_getViewportInsetBottom(env, delegate); } void ViewAndroid::OnBrowserControlsHeightChanged() { if (event_handler_) event_handler_->OnBrowserControlsHeightChanged(); for (auto* child : children_) { if (child->match_parent()) child->OnBrowserControlsHeightChanged(); } } void ViewAndroid::OnVerticalScrollDirectionChanged(bool direction_up, float current_scroll_ratio) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return; JNIEnv* env = base::android::AttachCurrentThread(); Java_ViewAndroidDelegate_onVerticalScrollDirectionChanged( env, delegate, direction_up, current_scroll_ratio); } void ViewAndroid::OnSizeChanged(int width, int height) { // Match-parent view must not receive size events. DCHECK(!match_parent()); float scale = GetDipScale(); gfx::Size size(std::ceil(width / scale), std::ceil(height / scale)); if (bounds_.size() == size) return; OnSizeChangedInternal(size); // Signal resize event after all the views in the tree get the updated size. DispatchOnSizeChanged(); } void ViewAndroid::OnSizeChangedInternal(const gfx::Size& size) { if (bounds_.size() == size) return; bounds_.set_size(size); for (auto* child : children_) { if (child->match_parent()) child->OnSizeChangedInternal(size); } } void ViewAndroid::DispatchOnSizeChanged() { if (event_handler_) event_handler_->OnSizeChanged(); for (auto* child : children_) { if (child->match_parent()) child->DispatchOnSizeChanged(); } } void ViewAndroid::OnPhysicalBackingSizeChanged( const gfx::Size& size, absl::optional deadline_override) { if (physical_size_ == size) return; physical_size_ = size; if (event_handler_) event_handler_->OnPhysicalBackingSizeChanged(deadline_override); for (auto* child : children_) child->OnPhysicalBackingSizeChanged(size, deadline_override); } void ViewAndroid::OnControlsResizeViewChanged(bool controls_resize_view) { if (controls_resize_view == controls_resize_view_) return; controls_resize_view_ = controls_resize_view; if (event_handler_) event_handler_->OnControlsResizeViewChanged(); for (auto* child : children_) child->OnControlsResizeViewChanged(controls_resize_view); } gfx::Size ViewAndroid::GetPhysicalBackingSize() const { return physical_size_; } gfx::Size ViewAndroid::GetSize() const { return bounds_.size(); } bool ViewAndroid::OnDragEvent(const DragEventAndroid& event) { return HitTest(base::BindRepeating(&ViewAndroid::SendDragEventToHandler), event, event.location()); } // static bool ViewAndroid::SendDragEventToHandler(EventHandlerAndroid* handler, const DragEventAndroid& event) { return handler->OnDragEvent(event); } bool ViewAndroid::OnTouchEvent(const MotionEventAndroid& event) { return HitTest(base::BindRepeating(&ViewAndroid::SendTouchEventToHandler), event, event.GetPoint()); } // static bool ViewAndroid::SendTouchEventToHandler(EventHandlerAndroid* handler, const MotionEventAndroid& event) { return handler->OnTouchEvent(event); } bool ViewAndroid::OnMouseEvent(const MotionEventAndroid& event) { return HitTest(base::BindRepeating(&ViewAndroid::SendMouseEventToHandler), event, event.GetPoint()); } // static bool ViewAndroid::SendMouseEventToHandler(EventHandlerAndroid* handler, const MotionEventAndroid& event) { return handler->OnMouseEvent(event); } bool ViewAndroid::OnMouseWheelEvent(const MotionEventAndroid& event) { return HitTest( base::BindRepeating(&ViewAndroid::SendMouseWheelEventToHandler), event, event.GetPoint()); } // static bool ViewAndroid::SendMouseWheelEventToHandler( EventHandlerAndroid* handler, const MotionEventAndroid& event) { return handler->OnMouseWheelEvent(event); } bool ViewAndroid::OnGestureEvent(const GestureEventAndroid& event) { return HitTest(base::BindRepeating(&ViewAndroid::SendGestureEventToHandler), event, event.location()); } // static bool ViewAndroid::SendGestureEventToHandler(EventHandlerAndroid* handler, const GestureEventAndroid& event) { return handler->OnGestureEvent(event); } bool ViewAndroid::OnGenericMotionEvent(const MotionEventAndroid& event) { if (event_handler_ && event_handler_->OnGenericMotionEvent(event)) return true; for (auto* child : children_) { if (child->OnGenericMotionEvent(event)) return true; } return false; } bool ViewAndroid::OnKeyUp(const KeyEventAndroid& event) { if (event_handler_ && event_handler_->OnKeyUp(event)) return true; for (auto* child : children_) { if (child->OnKeyUp(event)) return true; } return false; } bool ViewAndroid::DispatchKeyEvent(const KeyEventAndroid& event) { if (event_handler_ && event_handler_->DispatchKeyEvent(event)) return true; for (auto* child : children_) { if (child->DispatchKeyEvent(event)) return true; } return false; } bool ViewAndroid::ScrollBy(float delta_x, float delta_y) { if (event_handler_ && event_handler_->ScrollBy(delta_x, delta_y)) return true; for (auto* child : children_) { if (child->ScrollBy(delta_x, delta_y)) return true; } return false; } bool ViewAndroid::ScrollTo(float x, float y) { if (event_handler_ && event_handler_->ScrollTo(x, y)) return true; for (auto* child : children_) { if (child->ScrollTo(x, y)) return true; } return false; } void ViewAndroid::NotifyVirtualKeyboardOverlayRect( const gfx::Rect& keyboard_rect) { if (event_handler_) event_handler_->NotifyVirtualKeyboardOverlayRect(keyboard_rect); for (auto* child : children_) { child->NotifyVirtualKeyboardOverlayRect(keyboard_rect); } } template bool ViewAndroid::HitTest(EventHandlerCallback handler_callback, const E& event, const gfx::PointF& point) { if (event_handler_) { if (bounds_.origin().IsOrigin()) { // (x, y) == (0, 0) if (handler_callback.Run(event_handler_.get(), event)) return true; } else { std::unique_ptr e(event.CreateFor(point)); if (handler_callback.Run(event_handler_.get(), *e)) return true; } } if (!children_.empty()) { gfx::PointF offset_point(point); offset_point.Offset(-bounds_.x(), -bounds_.y()); gfx::Point int_point = gfx::ToFlooredPoint(offset_point); // Match from back to front for hit testing. for (auto* child : base::Reversed(children_)) { bool matched = child->match_parent(); if (!matched) matched = child->bounds_.Contains(int_point); if (matched && child->HitTest(handler_callback, event, offset_point)) return true; } } return false; } void ViewAndroid::SetLayoutForTesting(int x, int y, int width, int height) { bounds_.SetRect(x, y, width, height); } } // namespace ui