public final class

SoftwareKeyboardControllerCompat

extends java.lang.Object

 java.lang.Object

↳androidx.core.view.SoftwareKeyboardControllerCompat

Gradle dependencies

compile group: 'androidx.core', name: 'core', version: '1.15.0-alpha02'

  • groupId: androidx.core
  • artifactId: core
  • version: 1.15.0-alpha02

Artifact androidx.core:core:1.15.0-alpha02 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.core:core com.android.support:support-compat

Overview

Provide controls for showing and hiding the IME.

This class provides the implementation for WindowInsetsControllerCompat.show(int) and WindowInsetsControllerCompat.hide(int) for the WindowInsetsCompat.Type.ime().

This class only requires a View as a dependency, whereas WindowInsetsControllerCompat requires a Window for all of its behavior.

Summary

Constructors
publicSoftwareKeyboardControllerCompat(View view)

Methods
public voidhide()

Hide the software keyboard.

public voidshow()

Request that the system show a software keyboard.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public SoftwareKeyboardControllerCompat(View view)

Methods

public void show()

Request that the system show a software keyboard.

This request is best effort. If the system can currently show a software keyboard, it will be shown. However, there is no guarantee that the system will be able to show a software keyboard. If the system cannot show a software keyboard currently, this call will be silently ignored.

public void hide()

Hide the software keyboard.

This request is best effort, if the system cannot hide the software keyboard this call will silently be ignored.

Source

/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.core.view;

import static android.os.Build.VERSION.SDK_INT;

import android.content.Context;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.inputmethod.InputMethodManager;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Provide controls for showing and hiding the IME.
 * <p>
 * This class provides the implementation for {@link WindowInsetsControllerCompat#show(int)} and
 * {@link WindowInsetsControllerCompat#hide(int)} for the {@link WindowInsetsCompat.Type#ime()}.
 * <p>
 * This class only requires a View as a dependency, whereas {@link WindowInsetsControllerCompat}
 * requires a Window for all of its behavior.
 */
public final class SoftwareKeyboardControllerCompat {

    private final Impl mImpl;

    public SoftwareKeyboardControllerCompat(@NonNull View view) {
        if (SDK_INT >= 30) {
            mImpl = new Impl30(view);
        } else if (SDK_INT >= 20) {
            mImpl = new Impl20(view);
        } else {
            mImpl = new Impl();
        }
    }

    @RequiresApi(30)
    @Deprecated
    SoftwareKeyboardControllerCompat(@NonNull WindowInsetsController windowInsetsController) {
        mImpl = new Impl30(windowInsetsController);
    }

    /**
     * Request that the system show a software keyboard.
     * <p>
     * This request is best effort. If the system can currently show a software keyboard, it
     * will be shown. However, there is no guarantee that the system will be able to show a
     * software keyboard. If the system cannot show a software keyboard currently,
     * this call will be silently ignored.
     */
    public void show() {
        mImpl.show();
    }

    /**
     * Hide the software keyboard.
     * <p>
     * This request is best effort, if the system cannot hide the software keyboard this call
     * will silently be ignored.
     */
    public void hide() {
        mImpl.hide();
    }

    private static class Impl {
        Impl() {
            //private
        }

        void show() {
        }

        void hide() {
        }
    }

    @RequiresApi(20)
    private static class Impl20 extends Impl {

        @Nullable
        private final View mView;

        Impl20(@Nullable View view) {
            mView = view;
        }

        @Override
        void show() {
            // We'll try to find an available textView to focus to show the IME
            View view = mView;

            if (view == null) {
                return;
            }

            if (view.isInEditMode() || view.onCheckIsTextEditor()) {
                // The IME needs a text view to be focused to be shown
                // The view given to retrieve this controller is a textView so we can assume
                // that we can focus it in order to show the IME
                view.requestFocus();
            } else {
                view = view.getRootView().findFocus();
            }

            // Fallback on the container view
            if (view == null) {
                view = mView.getRootView().findViewById(android.R.id.content);
            }

            if (view != null && view.hasWindowFocus()) {
                final View finalView = view;
                finalView.post(() -> {
                    InputMethodManager imm =
                            (InputMethodManager) finalView.getContext()
                                    .getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.showSoftInput(finalView, 0);

                });
            }
        }

        @Override
        void hide() {
            if (mView != null) {
                ((InputMethodManager) mView.getContext()
                        .getSystemService(Context.INPUT_METHOD_SERVICE))
                        .hideSoftInputFromWindow(mView.getWindowToken(),
                                0);
            }
        }
    }

    @RequiresApi(30)
    private static class Impl30 extends Impl20 {

        @Nullable
        private View mView;

        @Nullable
        private WindowInsetsController mWindowInsetsController;

        Impl30(@NonNull View view) {
            super(view);
            mView = view;
        }

        Impl30(@Nullable WindowInsetsController windowInsetsController) {
            super(null);
            mWindowInsetsController = windowInsetsController;
        }

        @Override
        void show() {
            if (mView != null && SDK_INT < 33) {
                InputMethodManager imm =
                        (InputMethodManager) mView.getContext()
                                .getSystemService(Context.INPUT_METHOD_SERVICE);

                // This is a strange-looking workaround by making a call and ignoring the result.
                // We don't use the return value here, but isActive() has the side-effect of
                // calling a hidden method checkFocus(), which ensures that the IME state has the
                // correct view in some situations (especially when the focused view changes).
                // This is essentially a backport, since an equivalent checkFocus() call was
                // added in API 32 to improve behavior and an additional change in API 33:
                // https://issuetracker.google.com/issues/189858204
                imm.isActive();
            }
            WindowInsetsController insetsController = null;
            if (mWindowInsetsController != null) {
                insetsController = mWindowInsetsController;
            } else if (mView != null) {
                insetsController = mView.getWindowInsetsController();
            }
            if (insetsController != null) {
                insetsController.show(WindowInsets.Type.ime());
            }
            // InputMethodManager.showSoftInput() will also send the current toolType info to IME.
            // We always call it so that it can report update toolType correctly.
            super.show();
        }

        @Override
        void hide() {
            WindowInsetsController insetsController = null;
            if (mWindowInsetsController != null) {
                insetsController = mWindowInsetsController;
            } else if (mView != null) {
                insetsController = mView.getWindowInsetsController();
            }
            if (insetsController != null) {
                final AtomicBoolean isImeInsetsControllable = new AtomicBoolean(false);
                final WindowInsetsController.OnControllableInsetsChangedListener listener =
                        (windowInsetsController, typeMask) -> isImeInsetsControllable.set(
                                (typeMask & WindowInsetsCompat.Type.IME) != 0);
                // Register the OnControllableInsetsChangedListener would synchronously
                // callback current controllable insets. Adding the listener here to check if
                // ime inset is controllable.
                insetsController.addOnControllableInsetsChangedListener(listener);
                if (!isImeInsetsControllable.get() && mView != null) {
                    final InputMethodManager imm = (InputMethodManager) mView.getContext()
                            .getSystemService(Context.INPUT_METHOD_SERVICE);
                    // This is a backport when the app is in multi-windowing mode, it cannot
                    // control the ime insets. Use the InputMethodManager instead.
                    // TODO(b/280532442): Fix this in the platform side.
                    imm.hideSoftInputFromWindow(mView.getWindowToken(), 0);
                }
                insetsController.removeOnControllableInsetsChangedListener(listener);
                insetsController.hide(WindowInsets.Type.ime());
            } else {
                // Couldn't find an insets controller, fallback to old implementation
                super.hide();
            }
        }
    }
}