chromium/ui/android/java/src/org/chromium/ui/base/ActivityKeyboardVisibilityDelegate.java

// 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.ui.base;

import android.app.Activity;
import android.view.View;

import androidx.annotation.Nullable;

import org.chromium.base.Callback;
import org.chromium.base.supplier.LazyOneshotSupplier;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.ui.KeyboardVisibilityDelegate;

import java.lang.ref.WeakReference;

/**
 * A {@link KeyboardVisibilityDelegate} that listens to a given activity for layout and keyboard
 * inset changes. It notifies {@link KeyboardVisibilityDelegate.KeyboardVisibilityListener} whenever
 * the layout or keyboard inset change is suspected to be related to a software keyboard changing
 * visibility.
 */
public class ActivityKeyboardVisibilityDelegate extends KeyboardVisibilityDelegate
        implements View.OnLayoutChangeListener {
    private final Callback<Integer> mOnKeyboardInsetChanged = this::onKeyboardInsetChanged;

    private WeakReference<Activity> mActivity;
    private LazyOneshotSupplier<ObservableSupplier<Integer>> mLazyKeyboardInsetSupplier;
    private boolean mIsKeyboardShowing;
    private View mContentViewForTesting;

    /**
     * Creates a new delegate listening to the given activity. If the activity is destroyed, it will
     * continue to work as a regular {@link KeyboardVisibilityDelegate}.
     *
     * @param activity A {@link WeakReference} to an {@link Activity}.
     */
    public ActivityKeyboardVisibilityDelegate(WeakReference<Activity> activity) {
        mActivity = activity;
    }

    /** Sets the keyboard inset supplier. */
    void setLazyKeyboardInsetSupplier(
            LazyOneshotSupplier<ObservableSupplier<Integer>> lazyKeyboardInsetSupplier) {
        assert lazyKeyboardInsetSupplier != null;
        assert mLazyKeyboardInsetSupplier == null;
        mLazyKeyboardInsetSupplier = lazyKeyboardInsetSupplier;
        if (hasKeyboardVisibilityListeners()) {
            mLazyKeyboardInsetSupplier.get().addObserver(mOnKeyboardInsetChanged);
        }
    }

    public @Nullable Activity getActivity() {
        return mActivity.get();
    }

    @Override
    public void registerKeyboardVisibilityCallbacks() {
        Activity activity = getActivity();
        if (activity == null) return;
        View content = getContentView(activity);
        mIsKeyboardShowing = isKeyboardShowing(activity, content);
        content.addOnLayoutChangeListener(this);

        if (mLazyKeyboardInsetSupplier == null) return;

        mLazyKeyboardInsetSupplier.get().addObserver(mOnKeyboardInsetChanged);
    }

    @Override
    public void unregisterKeyboardVisibilityCallbacks() {
        Activity activity = getActivity();
        if (activity == null) return;
        getContentView(activity).removeOnLayoutChangeListener(this);

        if (mLazyKeyboardInsetSupplier == null) return;

        mLazyKeyboardInsetSupplier.get().removeObserver(mOnKeyboardInsetChanged);
    }

    @Override
    public void onLayoutChange(
            View v,
            int left,
            int top,
            int right,
            int bottom,
            int oldLeft,
            int oldTop,
            int oldRight,
            int oldBottom) {
        Activity activity = getActivity();
        if (activity == null) return;
        View content = getContentView(activity);
        updateKeyboardShowing(isKeyboardShowing(activity, content));
    }

    private void onKeyboardInsetChanged(int inset) {
        updateKeyboardShowing(inset > 0);
    }

    private void updateKeyboardShowing(boolean isShowing) {
        if (mIsKeyboardShowing == isShowing) return;
        mIsKeyboardShowing = isShowing;
        notifyListeners(isShowing);
    }

    private View getContentView(Activity activity) {
        if (mContentViewForTesting != null) return mContentViewForTesting;

        return activity.findViewById(android.R.id.content);
    }

    void setContentViewForTesting(View contentView) {
        mContentViewForTesting = contentView;
    }
}