chromium/content/public/android/java/src/org/chromium/content/browser/device_posture/WindowLayoutInfoListener.java

// Copyright 2024 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.device_posture;

import android.content.Context;
import android.os.Build;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.window.extensions.core.util.function.Consumer;
import androidx.window.extensions.layout.WindowLayoutInfo;

import org.chromium.base.ObserverList;
import org.chromium.base.UnownedUserData;
import org.chromium.base.UnownedUserDataHost;
import org.chromium.base.UnownedUserDataKey;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.window.WindowUtil;

/**
 * WindowLayoutInfoListener This class listen for WindowLayoutInfo changes and inform the device
 * posture service with the values.
 */
public class WindowLayoutInfoListener implements UnownedUserData {
    private static final UnownedUserDataKey<WindowLayoutInfoListener> KEY =
            new UnownedUserDataKey<>(WindowLayoutInfoListener.class);
    private final Consumer<WindowLayoutInfo> mWindowLayoutInfoChangedCallback;
    private WindowAndroid mWindowAndroid;
    private ObserverList<DevicePosturePlatformProviderAndroid> mObservers = new ObserverList<>();
    private WindowLayoutInfo mCurrentWindowLayoutInfo;

    private WindowLayoutInfoListener(WindowAndroid window) {
        assert window != null;
        mWindowLayoutInfoChangedCallback = this::onWindowLayoutInfoChanged;
        mWindowAndroid = window;
    }

    private void onWindowLayoutInfoChanged(WindowLayoutInfo windowLayoutInfo) {
        mCurrentWindowLayoutInfo = windowLayoutInfo;
        for (DevicePosturePlatformProviderAndroid observer : mObservers) {
            observer.onWindowLayoutInfoChanged(mCurrentWindowLayoutInfo);
        }
    }

    @Override
    public void onDetachedFromHost(UnownedUserDataHost host) {
        mWindowAndroid = null;
        WindowUtil.removeWindowLayoutInfoListener(mWindowLayoutInfoChangedCallback);
    }

    public void addObserver(DevicePosturePlatformProviderAndroid observer) {
        assert !mObservers.hasObserver(observer);
        Context context = mWindowAndroid.getContext().get();
        if (mObservers.isEmpty() && context != null) {
            WindowUtil.addWindowLayoutInfoListener(context, mWindowLayoutInfoChangedCallback);
        }

        mObservers.addObserver(observer);
        if (mCurrentWindowLayoutInfo != null) {
            // Notify the new observer right away with the current state.
            observer.onWindowLayoutInfoChanged(mCurrentWindowLayoutInfo);
        }
    }

    public void removeObserver(DevicePosturePlatformProviderAndroid observer) {
        assert mObservers.hasObserver(observer);
        mObservers.removeObserver(observer);
        if (mObservers.isEmpty()) {
            WindowUtil.removeWindowLayoutInfoListener(mWindowLayoutInfoChangedCallback);
            mCurrentWindowLayoutInfo = null;
        }
    }

    /**
     * Attach an instance of WindowLayoutInfoListener to the activity's window user data and return
     * it.
     */
    @RequiresApi(Build.VERSION_CODES.S) // For isUiContext()
    static @Nullable WindowLayoutInfoListener getWindowLayoutListenerForWindow(
            WindowAndroid window) {
        Context context = window.getContext().get();
        if (context == null || !context.isUiContext()) {
            return null;
        }
        WindowLayoutInfoListener listener =
                KEY.retrieveDataFromHost(window.getUnownedUserDataHost());
        if (listener == null) {
            listener = new WindowLayoutInfoListener(window);
            KEY.attachToHost(window.getUnownedUserDataHost(), listener);
        }
        return listener;
    }
}