// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content.browser;
import android.content.res.Configuration;
import org.chromium.base.ActivityState;
import org.chromium.base.ObserverList;
import org.chromium.base.UserData;
import org.chromium.content.browser.webcontents.WebContentsImpl;
import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.display.DisplayAndroid;
import org.chromium.ui.display.DisplayAndroid.DisplayAndroidObserver;
/** Manages {@link WindowEventObserver} instances used for WebContents. */
public final class WindowEventObserverManager implements DisplayAndroidObserver, UserData {
private final ObserverList<WindowEventObserver> mWindowEventObservers = new ObserverList<>();
private WindowAndroid mWindowAndroid;
private ViewEventSinkImpl mViewEventSink;
private boolean mAttachedToWindow;
// The cache of device's current orientation and DIP scale factor.
private int mRotation;
private float mDipScale;
private static final class UserDataFactoryLazyHolder {
private static final UserDataFactory<WindowEventObserverManager> INSTANCE =
WindowEventObserverManager::new;
}
public static WindowEventObserverManager from(WebContents webContents) {
return ((WebContentsImpl) webContents)
.getOrSetUserData(
WindowEventObserverManager.class, UserDataFactoryLazyHolder.INSTANCE);
}
private WindowEventObserverManager(WebContents webContents) {
mViewEventSink = ViewEventSinkImpl.from(webContents);
WindowAndroid window = webContents.getTopLevelNativeWindow();
if (window != null) onWindowAndroidChanged(window);
addObserver((WebContentsImpl) webContents);
}
/**
* Add {@link WindowEventObserver} object.
* @param observer Observer instance to add.
*/
public void addObserver(WindowEventObserver observer) {
assert !mWindowEventObservers.hasObserver(observer);
mWindowEventObservers.addObserver(observer);
if (mAttachedToWindow) observer.onAttachedToWindow();
}
/**
* Remove {@link WindowEventObserver} object.
* @param observer Observer instance to remove.
*/
public void removeObserver(WindowEventObserver observer) {
assert mWindowEventObservers.hasObserver(observer);
mWindowEventObservers.removeObserver(observer);
}
/**
* @see android.view.View#onAttachedToWindow()
*/
public void onAttachedToWindow() {
mAttachedToWindow = true;
addUiObservers();
for (WindowEventObserver observer : mWindowEventObservers) observer.onAttachedToWindow();
}
/**
* @see android.view.View#onDetachedFromWindow()
*/
public void onDetachedFromWindow() {
removeUiObservers();
mAttachedToWindow = false;
for (WindowEventObserver observer : mWindowEventObservers) observer.onDetachedFromWindow();
}
/**
* @see android.view.View#onWindowFocusChanged()
*/
public void onWindowFocusChanged(boolean hasWindowFocus) {
for (WindowEventObserver observer : mWindowEventObservers) {
observer.onWindowFocusChanged(hasWindowFocus);
}
}
/**
* Called when {@link WindowAndroid} for WebContents is updated.
* @param windowAndroid A new WindowAndroid object.
*/
public void onWindowAndroidChanged(WindowAndroid windowAndroid) {
if (windowAndroid == mWindowAndroid) return;
removeUiObservers();
mWindowAndroid = windowAndroid;
addUiObservers();
for (WindowEventObserver observer : mWindowEventObservers) {
observer.onWindowAndroidChanged(windowAndroid);
}
}
public void onConfigurationChanged(Configuration newConfig) {
for (WindowEventObserver observer : mWindowEventObservers) {
observer.onConfigurationChanged(newConfig);
}
}
public void onViewFocusChanged(boolean gainFocus, boolean hideKeyboardOnBlur) {
for (WindowEventObserver observer : mWindowEventObservers) {
observer.onViewFocusChanged(gainFocus, hideKeyboardOnBlur);
}
}
private void addDisplayAndroidObserverIfNeeded() {
if (!mAttachedToWindow || mWindowAndroid == null) return;
DisplayAndroid display = mWindowAndroid.getDisplay();
display.addObserver(this);
onRotationChanged(display.getRotation());
onDIPScaleChanged(display.getDipScale());
}
private void addActivityStateObserver() {
if (!mAttachedToWindow || mWindowAndroid == null) return;
mWindowAndroid.addActivityStateObserver(mViewEventSink);
// Sets the state of ViewEventSink right if activity is already in resumed state.
// Can happen when the front tab gets moved down in the stack while Chrome
// is in background. See https://crbug.com/852336.
if (mWindowAndroid.getActivityState() == ActivityState.RESUMED) {
mViewEventSink.onActivityResumed();
}
}
private void addUiObservers() {
addDisplayAndroidObserverIfNeeded();
addActivityStateObserver();
}
private void removeUiObservers() {
removeDisplayAndroidObserver();
removeActivityStateObserver();
}
private void removeDisplayAndroidObserver() {
if (mWindowAndroid == null) return;
mWindowAndroid.getDisplay().removeObserver(this);
}
private void removeActivityStateObserver() {
if (!mAttachedToWindow || mWindowAndroid == null) return;
mWindowAndroid.removeActivityStateObserver(mViewEventSink);
}
// DisplayAndroidObserver
@Override
public void onRotationChanged(int rotation) {
if (mRotation == rotation) return;
mRotation = rotation;
for (WindowEventObserver observer : mWindowEventObservers) {
observer.onRotationChanged(rotation);
}
}
@Override
public void onDIPScaleChanged(float dipScale) {
if (mDipScale == dipScale) return;
mDipScale = dipScale;
for (WindowEventObserver observer : mWindowEventObservers) {
observer.onDIPScaleChanged(dipScale);
}
}
}