blob: 534059d73a3f56476d6036c17ad2b56b7cb0fbc1 [file] [log] [blame]
// Copyright 2018 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 "chrome/browser/ui/android/overlay/overlay_window_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/memory/ptr_util.h"
#include "cc/layers/surface_layer.h"
#include "chrome/android/chrome_jni_headers/PictureInPictureActivity_jni.h"
#include "chrome/browser/android/tab_android.h"
#include "components/thin_webview/compositor_view.h"
#include "content/public/browser/overlay_window.h"
#include "content/public/browser/video_picture_in_picture_window_controller.h"
#include "content/public/browser/web_contents.h"
#include "ui/android/window_android_compositor.h"
// static
std::unique_ptr<content::VideoOverlayWindow>
content::VideoOverlayWindow::Create(
VideoPictureInPictureWindowController* controller) {
return std::make_unique<OverlayWindowAndroid>(controller);
}
// static
std::unique_ptr<content::DocumentOverlayWindow>
content::DocumentOverlayWindow::Create(
DocumentPictureInPictureWindowController* controller) {
return nullptr;
}
OverlayWindowAndroid::OverlayWindowAndroid(
content::VideoPictureInPictureWindowController* controller)
: window_android_(nullptr),
compositor_view_(nullptr),
surface_layer_(cc::SurfaceLayer::Create()),
bounds_(gfx::Rect(0, 0)),
update_action_timer_(std::make_unique<base::OneShotTimer>()),
controller_(controller) {
surface_layer_->SetIsDrawable(true);
surface_layer_->SetStretchContentToFillBounds(true);
surface_layer_->SetMayContainVideo(true);
surface_layer_->SetBackgroundColor(SkColors::kBlack);
auto* web_contents = controller_->GetWebContents();
gfx::Rect source_bounds = controller_->GetSourceBounds();
gfx::Rect unscaled_content_bounds = web_contents->GetContainerBounds();
auto* native_view = web_contents->GetNativeView();
// The java side will ignore any source bounds that are not on the screen for
// the source rect hint. It will use the aspect ratio only in that case. If
// it's not entirely on-screen, then skip it.
const float dip_scale = native_view->GetDipScale();
gfx::Rect content_bounds(unscaled_content_bounds.x() * dip_scale,
unscaled_content_bounds.y() * dip_scale,
unscaled_content_bounds.width() * dip_scale,
unscaled_content_bounds.height() * dip_scale);
const bool out_of_bounds = !content_bounds.Contains(source_bounds);
if (!out_of_bounds) {
// Convert to screen space.
gfx::PointF offset = native_view->GetLocationOnScreen(0, 0);
source_bounds.Offset(
static_cast<int>(offset.x()),
static_cast<int>(offset.y()) +
native_view->content_offset() * native_view->GetDipScale());
} else {
// Slide this offscreen, while keeping the aspect ratio the same.
source_bounds.set_x(-1);
}
JNIEnv* env = base::android::AttachCurrentThread();
Java_PictureInPictureActivity_createActivity(
env, reinterpret_cast<intptr_t>(this),
TabAndroid::FromWebContents(web_contents)->GetJavaObject(),
source_bounds.x(), source_bounds.y(), source_bounds.width(),
source_bounds.height());
}
OverlayWindowAndroid::~OverlayWindowAndroid() {
JNIEnv* env = base::android::AttachCurrentThread();
Java_PictureInPictureActivity_onWindowDestroyed(
env, reinterpret_cast<intptr_t>(this));
}
void OverlayWindowAndroid::OnActivityStart(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
const base::android::JavaParamRef<jobject>& jwindow_android) {
java_ref_ = JavaObjectWeakGlobalRef(env, obj);
window_android_ = ui::WindowAndroid::FromJavaWindowAndroid(jwindow_android);
window_android_->AddObserver(this);
Java_PictureInPictureActivity_setPlaybackState(env, java_ref_.get(env),
playback_state_);
Java_PictureInPictureActivity_setMicrophoneMuted(env, java_ref_.get(env),
microphone_muted_);
Java_PictureInPictureActivity_setCameraState(env, java_ref_.get(env),
camera_on_);
if (!update_action_timer_->IsRunning())
MaybeNotifyVisibleActionsChanged();
if (video_size_.IsEmpty())
return;
Java_PictureInPictureActivity_updateVideoSize(
env, java_ref_.get(env), video_size_.width(), video_size_.height());
}
void OverlayWindowAndroid::OnAttachCompositor() {
window_android_->GetCompositor()->AddChildFrameSink(
surface_layer_->surface_id().frame_sink_id());
}
void OverlayWindowAndroid::OnDetachCompositor() {
window_android_->GetCompositor()->RemoveChildFrameSink(
surface_layer_->surface_id().frame_sink_id());
}
void OverlayWindowAndroid::OnActivityStopped() {
Destroy(nullptr);
}
void OverlayWindowAndroid::Destroy(JNIEnv* env) {
java_ref_.reset();
if (window_android_) {
window_android_->RemoveObserver(this);
window_android_ = nullptr;
}
// Only pause the video when play/pause button is visible.
controller_->OnWindowDestroyed(
/*should_pause_video=*/visible_actions_.find(
static_cast<int>(media_session::mojom::MediaSessionAction::kPlay)) !=
visible_actions_.end());
}
void OverlayWindowAndroid::TogglePlayPause(JNIEnv* env) {
DCHECK(!controller_->IsPlayerActive());
controller_->TogglePlayPause();
}
void OverlayWindowAndroid::NextTrack(JNIEnv* env) {
controller_->NextTrack();
}
void OverlayWindowAndroid::PreviousTrack(JNIEnv* env) {
controller_->PreviousTrack();
}
void OverlayWindowAndroid::ToggleMicrophone(JNIEnv* env) {
controller_->ToggleMicrophone();
}
void OverlayWindowAndroid::ToggleCamera(JNIEnv* env) {
controller_->ToggleCamera();
}
void OverlayWindowAndroid::HangUp(JNIEnv* env) {
controller_->HangUp();
}
void OverlayWindowAndroid::CompositorViewCreated(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& compositor_view) {
compositor_view_ =
thin_webview::android::CompositorView::FromJavaObject(compositor_view);
DCHECK(compositor_view_);
compositor_view_->SetRootLayer(surface_layer_);
}
void OverlayWindowAndroid::OnViewSizeChanged(JNIEnv* env,
jint width,
jint height) {
gfx::Size content_size(width, height);
if (bounds_.size() == content_size)
return;
bounds_.set_size(content_size);
surface_layer_->SetBounds(content_size);
controller_->UpdateLayerBounds();
}
void OverlayWindowAndroid::OnBackToTab(JNIEnv* env) {
controller_->FocusInitiator();
Hide();
}
void OverlayWindowAndroid::Close() {
CloseInternal();
controller_->OnWindowDestroyed(/*should_pause_video=*/true);
}
void OverlayWindowAndroid::Hide() {
CloseInternal();
controller_->OnWindowDestroyed(/*should_pause_video=*/false);
}
void OverlayWindowAndroid::CloseInternal() {
if (java_ref_.is_uninitialized())
return;
DCHECK(window_android_);
window_android_->RemoveObserver(this);
window_android_ = nullptr;
JNIEnv* env = base::android::AttachCurrentThread();
Java_PictureInPictureActivity_close(env, java_ref_.get(env));
}
bool OverlayWindowAndroid::IsActive() {
return true;
}
bool OverlayWindowAndroid::IsVisible() {
return true;
}
bool OverlayWindowAndroid::IsAlwaysOnTop() {
return true;
}
gfx::Rect OverlayWindowAndroid::GetBounds() {
return bounds_;
}
void OverlayWindowAndroid::UpdateNaturalSize(const gfx::Size& natural_size) {
if (java_ref_.is_uninitialized()) {
video_size_ = natural_size;
// This isn't guaranteed to be right, but it's better than (0,0).
bounds_.set_size(natural_size);
return;
}
JNIEnv* env = base::android::AttachCurrentThread();
Java_PictureInPictureActivity_updateVideoSize(
env, java_ref_.get(env), natural_size.width(), natural_size.height());
}
void OverlayWindowAndroid::SetPlaybackState(PlaybackState playback_state) {
if (playback_state_ == playback_state)
return;
playback_state_ = playback_state;
if (java_ref_.is_uninitialized())
return;
JNIEnv* env = base::android::AttachCurrentThread();
Java_PictureInPictureActivity_setPlaybackState(env, java_ref_.get(env),
playback_state);
}
void OverlayWindowAndroid::SetMicrophoneMuted(bool muted) {
if (microphone_muted_ == muted)
return;
microphone_muted_ = muted;
if (java_ref_.is_uninitialized())
return;
JNIEnv* env = base::android::AttachCurrentThread();
Java_PictureInPictureActivity_setMicrophoneMuted(env, java_ref_.get(env),
microphone_muted_);
}
void OverlayWindowAndroid::SetCameraState(bool turned_on) {
if (camera_on_ == turned_on)
return;
camera_on_ = turned_on;
if (java_ref_.is_uninitialized())
return;
JNIEnv* env = base::android::AttachCurrentThread();
Java_PictureInPictureActivity_setCameraState(env, java_ref_.get(env),
camera_on_);
}
void OverlayWindowAndroid::SetPlayPauseButtonVisibility(bool is_visible) {
MaybeUpdateVisibleAction(media_session::mojom::MediaSessionAction::kPlay,
is_visible);
}
void OverlayWindowAndroid::SetNextTrackButtonVisibility(bool is_visible) {
MaybeUpdateVisibleAction(media_session::mojom::MediaSessionAction::kNextTrack,
is_visible);
}
void OverlayWindowAndroid::SetPreviousTrackButtonVisibility(bool is_visible) {
MaybeUpdateVisibleAction(
media_session::mojom::MediaSessionAction::kPreviousTrack, is_visible);
}
void OverlayWindowAndroid::SetToggleMicrophoneButtonVisibility(
bool is_visible) {
MaybeUpdateVisibleAction(
media_session::mojom::MediaSessionAction::kToggleMicrophone, is_visible);
}
void OverlayWindowAndroid::SetToggleCameraButtonVisibility(bool is_visible) {
MaybeUpdateVisibleAction(
media_session::mojom::MediaSessionAction::kToggleCamera, is_visible);
}
void OverlayWindowAndroid::SetHangUpButtonVisibility(bool is_visible) {
MaybeUpdateVisibleAction(media_session::mojom::MediaSessionAction::kHangUp,
is_visible);
}
void OverlayWindowAndroid::SetSurfaceId(const viz::SurfaceId& surface_id) {
const viz::SurfaceId& old_surface_id = surface_layer_->surface_id().is_valid()
? surface_layer_->surface_id()
: surface_id;
if (window_android_ && window_android_->GetCompositor() &&
old_surface_id != surface_id) {
// On Android, the new frame sink needs to be added before
// removing the previous surface sink.
window_android_->GetCompositor()->AddChildFrameSink(
surface_id.frame_sink_id());
window_android_->GetCompositor()->RemoveChildFrameSink(
old_surface_id.frame_sink_id());
}
// Set the surface after frame sink hierarchy update.
surface_layer_->SetSurfaceId(surface_id,
cc::DeadlinePolicy::UseDefaultDeadline());
}
cc::Layer* OverlayWindowAndroid::GetLayerForTesting() {
return nullptr;
}
void OverlayWindowAndroid::MaybeNotifyVisibleActionsChanged() {
if (java_ref_.is_uninitialized())
return;
JNIEnv* env = base::android::AttachCurrentThread();
Java_PictureInPictureActivity_updateVisibleActions(
env, java_ref_.get(env),
base::android::ToJavaIntArray(
env,
std::vector<int>(visible_actions_.begin(), visible_actions_.end())));
}
void OverlayWindowAndroid::MaybeUpdateVisibleAction(
const media_session::mojom::MediaSessionAction& action,
bool is_visible) {
int action_code = static_cast<int>(action);
if ((visible_actions_.find(action_code) != visible_actions_.end()) ==
is_visible) {
return;
}
if (is_visible)
visible_actions_.insert(action_code);
else
visible_actions_.erase(action_code);
if (!update_action_timer_->IsRunning()) {
update_action_timer_->Start(
FROM_HERE, base::Seconds(1),
base::BindOnce(&OverlayWindowAndroid::MaybeNotifyVisibleActionsChanged,
base::Unretained(this)));
}
}