// 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 "base/android/jni_android.h" #include "base/android/jni_string.h" #include "base/containers/adapters.h" #include "cc/layers/layer.h" #include "jni/ViewAndroidDelegate_jni.h" #include "ui/android/event_forwarder.h" #include "ui/android/view_client.h" #include "ui/android/window_android.h" #include "ui/base/layout.h" #include "ui/events/android/motion_event_android.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(ViewClient* view_client) : parent_(nullptr), client_(view_client), layout_params_(LayoutParams::MatchParent()) {} ViewAndroid::ViewAndroid() : ViewAndroid(nullptr) {} ViewAndroid::~ViewAndroid() { RemoveFromParent(); for (std::list::iterator it = children_.begin(); it != children_.end(); it++) { DCHECK_EQ((*it)->parent_, this); (*it)->parent_ = nullptr; } } 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); } float ViewAndroid::GetDipScale() { return ui::GetScaleFactorForNativeView(this); } 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(std::find(children_.begin(), children_.end(), child) == children_.end()); 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_); } // 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::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) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return; float dip_scale = GetDipScale(); int left_margin = std::round(bounds.x() * dip_scale); int top_margin = std::round((content_offset().y() + bounds.y()) * dip_scale); JNIEnv* env = base::android::AttachCurrentThread(); Java_ViewAndroidDelegate_setViewPosition( env, delegate, anchor, bounds.x(), bounds.y(), bounds.width(), bounds.height(), dip_scale, 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); } void ViewAndroid::RemoveChild(ViewAndroid* child) { DCHECK(child); DCHECK_EQ(child->parent_, this); std::list::iterator it = std::find(children_.begin(), children_.end(), child); DCHECK(it != children_.end()); children_.erase(it); child->parent_ = nullptr; } 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(); } void ViewAndroid::SetLayer(scoped_refptr layer) { layer_ = layer; } void ViewAndroid::SetLayout(ViewAndroid::LayoutParams params) { layout_params_ = params; } bool ViewAndroid::StartDragAndDrop(const JavaRef& jtext, const JavaRef& jimage) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return false; JNIEnv* env = base::android::AttachCurrentThread(); return Java_ViewAndroidDelegate_startDragAndDrop(env, delegate, jtext, jimage); } 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) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return; JNIEnv* env = base::android::AttachCurrentThread(); Java_ViewAndroidDelegate_onTopControlsChanged( env, delegate, top_controls_offset, top_content_offset); } void ViewAndroid::OnBottomControlsChanged(float bottom_controls_offset, float bottom_content_offset) { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return; JNIEnv* env = base::android::AttachCurrentThread(); Java_ViewAndroidDelegate_onBottomControlsChanged( env, delegate, bottom_controls_offset, bottom_content_offset); } int ViewAndroid::GetSystemWindowInsetBottom() { ScopedJavaLocalRef delegate(GetViewAndroidDelegate()); if (delegate.is_null()) return 0; JNIEnv* env = base::android::AttachCurrentThread(); return Java_ViewAndroidDelegate_getSystemWindowInsetBottom(env, delegate); } void ViewAndroid::OnPhysicalBackingSizeChanged(const gfx::Size& size) { if (physical_size_ == size) return; physical_size_ = size; client_->OnPhysicalBackingSizeChanged(); for (auto* child : children_) child->OnPhysicalBackingSizeChanged(size); } gfx::Size ViewAndroid::GetPhysicalBackingSize() { return physical_size_; } bool ViewAndroid::OnTouchEvent(const MotionEventAndroid& event, bool for_touch_handle) { return HitTest( base::Bind(&ViewAndroid::SendTouchEventToClient, for_touch_handle), event); } bool ViewAndroid::SendTouchEventToClient(bool for_touch_handle, ViewClient* client, const MotionEventAndroid& event) { return client->OnTouchEvent(event, for_touch_handle); } bool ViewAndroid::OnMouseEvent(const MotionEventAndroid& event) { return HitTest(base::Bind(&ViewAndroid::SendMouseEventToClient), event); } // static bool ViewAndroid::SendMouseEventToClient(ViewClient* client, const MotionEventAndroid& event) { return client->OnMouseEvent(event); } // static bool ViewAndroid::OnMouseWheelEvent(const MotionEventAndroid& event) { return HitTest(base::Bind(&ViewAndroid::SendMouseWheelEventToClient), event); } // static bool ViewAndroid::SendMouseWheelEventToClient(ViewClient* client, const MotionEventAndroid& event) { return client->OnMouseWheelEvent(event); } bool ViewAndroid::HitTest(ViewClientCallback send_to_client, const MotionEventAndroid& event) { if (client_ && send_to_client.Run(client_, event)) return true; if (!children_.empty()) { std::unique_ptr e( event.Offset(-layout_params_.x, -layout_params_.y)); // Match from back to front for hit testing. for (auto* child : base::Reversed(children_)) { bool matched = child->layout_params_.match_parent; if (!matched) { gfx::Rect bound(child->layout_params_.x, child->layout_params_.y, child->layout_params_.width, child->layout_params_.height); matched = bound.Contains(e->GetX(0), e->GetY(0)); } if (matched && child->HitTest(send_to_client, *e)) return true; } } return false; } } // namespace ui