blob: 0dd48ae6c9e1f08b6eed845a2ccd9f4b9d7d5688 [file] [log] [blame]
/*
* Copyright (C) 2022 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 com.android.server.inputmethod;
import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
import static android.server.inputmethod.InputMethodManagerServiceProto.ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD;
import static android.server.inputmethod.InputMethodManagerServiceProto.INPUT_SHOWN;
import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_EXPLICITLY_REQUESTED;
import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_FORCED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.MotionEvent.TOOL_TYPE_UNKNOWN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
import static android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static com.android.internal.inputmethod.InputMethodDebug.softInputModeToString;
import static com.android.internal.inputmethod.SoftInputShowHideReason.REMOVE_IME_SCREENSHOT_FROM_IMMS;
import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_IME_SCREENSHOT_FROM_IMMS;
import static com.android.server.inputmethod.InputMethodManagerService.computeImeDisplayIdForTarget;
import android.accessibilityservice.AccessibilityService;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.IBinder;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.server.LocalServices;
import com.android.server.wm.ImeTargetChangeListener;
import com.android.server.wm.WindowManagerInternal;
import java.io.PrintWriter;
import java.util.WeakHashMap;
/**
* A computer used by {@link InputMethodManagerService} that computes the IME visibility state
* according the given {@link ImeTargetWindowState} from the focused window or the app requested IME
* visibility from {@link InputMethodManager}.
*/
public final class ImeVisibilityStateComputer {
private static final String TAG = "ImeVisibilityStateComputer";
private static final boolean DEBUG = InputMethodManagerService.DEBUG;
private final InputMethodManagerService mService;
private final WindowManagerInternal mWindowManagerInternal;
final InputMethodManagerService.ImeDisplayValidator mImeDisplayValidator;
/**
* A map used to track the requested IME target window and its state. The key represents the
* token of the window and the value is the corresponding IME window state.
*/
private final WeakHashMap<IBinder, ImeTargetWindowState> mRequestWindowStateMap =
new WeakHashMap<>();
/**
* Set if IME was explicitly told to show the input method.
*
* @see InputMethodManager#SHOW_IMPLICIT that we set the value is {@code false}.
* @see InputMethodManager#HIDE_IMPLICIT_ONLY that system will not hide IME when the value is
* {@code true}.
*/
boolean mRequestedShowExplicitly;
/**
* Set if we were forced to be shown.
*
* @see InputMethodManager#SHOW_FORCED
* @see InputMethodManager#HIDE_NOT_ALWAYS
*/
boolean mShowForced;
/**
* Set if we last told the input method to show itself.
*/
private boolean mInputShown;
/**
* Set if we called
* {@link com.android.server.wm.ImeTargetVisibilityPolicy#showImeScreenshot(IBinder, int)}.
*/
private boolean mRequestedImeScreenshot;
/** The window token of the current visible IME layering target overlay. */
private IBinder mCurVisibleImeLayeringOverlay;
/** The window token of the current visible IME input target. */
private IBinder mCurVisibleImeInputTarget;
/** Represent the invalid IME visibility state */
public static final int STATE_INVALID = -1;
/** State to handle hiding the IME window requested by the app. */
public static final int STATE_HIDE_IME = 0;
/** State to handle showing the IME window requested by the app. */
public static final int STATE_SHOW_IME = 1;
/** State to handle showing the IME window with making the overlay window above it. */
public static final int STATE_SHOW_IME_ABOVE_OVERLAY = 2;
/** State to handle showing the IME window with making the overlay window behind it. */
public static final int STATE_SHOW_IME_BEHIND_OVERLAY = 3;
/** State to handle showing an IME preview surface during the app was loosing the IME focus */
public static final int STATE_SHOW_IME_SNAPSHOT = 4;
public static final int STATE_HIDE_IME_EXPLICIT = 5;
public static final int STATE_HIDE_IME_NOT_ALWAYS = 6;
public static final int STATE_SHOW_IME_IMPLICIT = 7;
/** State to handle removing an IME preview surface when necessary. */
public static final int STATE_REMOVE_IME_SNAPSHOT = 8;
@IntDef({
STATE_INVALID,
STATE_HIDE_IME,
STATE_SHOW_IME,
STATE_SHOW_IME_ABOVE_OVERLAY,
STATE_SHOW_IME_BEHIND_OVERLAY,
STATE_SHOW_IME_SNAPSHOT,
STATE_HIDE_IME_EXPLICIT,
STATE_HIDE_IME_NOT_ALWAYS,
STATE_SHOW_IME_IMPLICIT,
STATE_REMOVE_IME_SNAPSHOT,
})
@interface VisibilityState {}
/**
* The policy to configure the IME visibility.
*/
private final ImeVisibilityPolicy mPolicy;
public ImeVisibilityStateComputer(@NonNull InputMethodManagerService service) {
this(service,
LocalServices.getService(WindowManagerInternal.class),
LocalServices.getService(WindowManagerInternal.class)::getDisplayImePolicy,
new ImeVisibilityPolicy());
}
@VisibleForTesting
public ImeVisibilityStateComputer(@NonNull InputMethodManagerService service,
@NonNull Injector injector) {
this(service, injector.getWmService(), injector.getImeValidator(),
new ImeVisibilityPolicy());
}
interface Injector {
default WindowManagerInternal getWmService() {
return null;
}
default InputMethodManagerService.ImeDisplayValidator getImeValidator() {
return null;
}
}
private ImeVisibilityStateComputer(InputMethodManagerService service,
WindowManagerInternal wmService,
InputMethodManagerService.ImeDisplayValidator imeDisplayValidator,
ImeVisibilityPolicy imePolicy) {
mService = service;
mWindowManagerInternal = wmService;
mImeDisplayValidator = imeDisplayValidator;
mPolicy = imePolicy;
mWindowManagerInternal.setInputMethodTargetChangeListener(new ImeTargetChangeListener() {
@Override
public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken,
@WindowManager.LayoutParams.WindowType int windowType, boolean visible,
boolean removed) {
mCurVisibleImeLayeringOverlay =
// Ignoring the starting window since it's ok to cover the IME target
// window in temporary without affecting the IME visibility.
(visible && !removed && windowType != TYPE_APPLICATION_STARTING)
? overlayWindowToken : null;
}
@Override
public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget,
boolean visibleRequested, boolean removed) {
if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested || removed)
&& mCurVisibleImeLayeringOverlay != null) {
mService.onApplyImeVisibilityFromComputer(imeInputTarget,
new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE));
}
mCurVisibleImeInputTarget = (visibleRequested && !removed) ? imeInputTarget : null;
}
});
}
/**
* Called when {@link InputMethodManagerService} is processing the show IME request.
*
* @param statsToken The token for tracking this show request.
* @return {@code true} when the show request can proceed.
*/
boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken,
@InputMethodManager.ShowFlags int showFlags) {
if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) {
ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
return false;
}
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
// We only "set" the state corresponding to the flags, as this will be reset
// in clearImeShowFlags during a hide request.
// Thus, we keep the strongest values set (e.g. an implicit show right after
// an explicit show will still be considered explicit, likewise for forced).
if ((showFlags & InputMethodManager.SHOW_FORCED) != 0) {
mRequestedShowExplicitly = true;
mShowForced = true;
} else if ((showFlags & InputMethodManager.SHOW_IMPLICIT) == 0) {
mRequestedShowExplicitly = true;
}
return true;
}
/**
* Called when {@link InputMethodManagerService} is processing the hide IME request.
*
* @param statsToken The token for tracking this hide request.
* @return {@code true} when the hide request can proceed.
*/
boolean canHideIme(@NonNull ImeTracker.Token statsToken,
@InputMethodManager.HideFlags int hideFlags) {
if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
&& (mRequestedShowExplicitly || mShowForced)) {
if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
return false;
}
if (mShowForced && (hideFlags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
return false;
}
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
return true;
}
/**
* Returns the show flags for IME. This translates from {@link InputMethodManager.ShowFlags}
* to {@link InputMethod.ShowFlags}.
*/
@InputMethod.ShowFlags
int getShowFlagsForInputMethodServiceOnly() {
int flags = 0;
if (mShowForced) {
flags |= InputMethod.SHOW_FORCED | InputMethod.SHOW_EXPLICIT;
} else if (mRequestedShowExplicitly) {
flags |= InputMethod.SHOW_EXPLICIT;
}
return flags;
}
/**
* Returns the show flags for IMM. This translates from {@link InputMethod.ShowFlags}
* to {@link InputMethodManager.ShowFlags}.
*/
@InputMethodManager.ShowFlags
int getShowFlags() {
int flags = 0;
if (mShowForced) {
flags |= InputMethodManager.SHOW_FORCED;
} else if (!mRequestedShowExplicitly) {
flags |= InputMethodManager.SHOW_IMPLICIT;
}
return flags;
}
void clearImeShowFlags() {
mRequestedShowExplicitly = false;
mShowForced = false;
mInputShown = false;
}
int computeImeDisplayId(@NonNull ImeTargetWindowState state, int displayId) {
final int displayToShowIme = computeImeDisplayIdForTarget(displayId, mImeDisplayValidator);
state.setImeDisplayId(displayToShowIme);
final boolean imeHiddenByPolicy = displayToShowIme == INVALID_DISPLAY;
mPolicy.setImeHiddenByDisplayPolicy(imeHiddenByPolicy);
return displayToShowIme;
}
/**
* Request to show/hide IME from the given window.
*
* @param windowToken The window which requests to show/hide IME.
* @param showIme {@code true} means to show IME, {@code false} otherwise.
* Note that in the computer will take this option to compute the
* visibility state, it could be {@link #STATE_SHOW_IME} or
* {@link #STATE_HIDE_IME}.
*/
void requestImeVisibility(IBinder windowToken, boolean showIme) {
ImeTargetWindowState state = getOrCreateWindowState(windowToken);
if (!mPolicy.mPendingA11yRequestingHideKeyboard) {
state.setRequestedImeVisible(showIme);
} else {
// As A11y requests no IME is just a temporary, so we don't change the requested IME
// visible in case the last visibility state goes wrong after leaving from the a11y
// policy.
mPolicy.mPendingA11yRequestingHideKeyboard = false;
}
// create a placeholder token for IMS so that IMS cannot inject windows into client app.
state.setRequestImeToken(new Binder());
setWindowStateInner(windowToken, state);
}
ImeTargetWindowState getOrCreateWindowState(IBinder windowToken) {
ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
if (state == null) {
state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNSPECIFIED, 0, false, false, false);
}
return state;
}
ImeTargetWindowState getWindowStateOrNull(IBinder windowToken) {
ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
return state;
}
void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
if (state != null && newState.hasEditorFocused()
&& newState.mToolType != MotionEvent.TOOL_TYPE_STYLUS) {
// Inherit the last requested IME visible state when the target window is still
// focused with an editor.
newState.setRequestedImeVisible(state.mRequestedImeVisible);
}
setWindowStateInner(windowToken, newState);
}
private void setWindowStateInner(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
if (DEBUG) Slog.d(TAG, "setWindowStateInner, windowToken=" + windowToken
+ ", state=" + newState);
mRequestWindowStateMap.put(windowToken, newState);
}
static class ImeVisibilityResult {
private final @VisibilityState int mState;
private final @SoftInputShowHideReason int mReason;
ImeVisibilityResult(@VisibilityState int state, @SoftInputShowHideReason int reason) {
mState = state;
mReason = reason;
}
@VisibilityState int getState() {
return mState;
}
@SoftInputShowHideReason int getReason() {
return mReason;
}
}
ImeVisibilityResult computeState(ImeTargetWindowState state, boolean allowVisible) {
// TODO: Output the request IME visibility state according to the requested window state
final int softInputVisibility = state.mSoftInputModeState & SOFT_INPUT_MASK_STATE;
// Should we auto-show the IME even if the caller has not
// specified what should be done with it?
// We only do this automatically if the window can resize
// to accommodate the IME (so what the user sees will give
// them good context without input information being obscured
// by the IME) or if running on a large screen where there
// is more room for the target window + IME.
final boolean doAutoShow =
(state.mSoftInputModeState & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
== WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|| mService.mRes.getConfiguration().isLayoutSizeAtLeast(
Configuration.SCREENLAYOUT_SIZE_LARGE);
final boolean isForwardNavigation = (state.mSoftInputModeState
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0;
// We shows the IME when the system allows the IME focused target window to restore the
// IME visibility (e.g. switching to the app task when last time the IME is visible).
// Note that we don't restore IME visibility for some cases (e.g. when the soft input
// state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation).
// Because the app might leverage these flags to hide soft-keyboard with showing their own
// UI for input.
if (state.hasEditorFocused() && shouldRestoreImeVisibility(state)) {
if (DEBUG) Slog.v(TAG, "Will show input to restore visibility");
// Inherit the last requested IME visible state when the target window is still
// focused with an editor.
state.setRequestedImeVisible(true);
setWindowStateInner(getWindowTokenFrom(state), state);
return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
}
switch (softInputVisibility) {
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
if (state.hasImeFocusChanged() && (!state.hasEditorFocused() || !doAutoShow)) {
if (WindowManager.LayoutParams.mayUseInputMethod(state.getWindowFlags())) {
// There is no focus view, and this window will
// be behind any soft input window, so hide the
// soft input window if it is shown.
if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
return new ImeVisibilityResult(STATE_HIDE_IME_NOT_ALWAYS,
SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);
}
} else if (state.hasEditorFocused() && doAutoShow && isForwardNavigation) {
// There is a focus view, and we are navigating forward
// into the window, so show the input window for the user.
// We only do this automatically if the window can resize
// to accommodate the IME (so what the user sees will give
// them good context without input information being obscured
// by the IME) or if running on a large screen where there
// is more room for the target window + IME.
if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
// Do nothing but preserving the last IME requested visibility state.
final ImeTargetWindowState lastState =
getWindowStateOrNull(mService.mLastImeTargetWindow);
if (lastState != null) {
state.setRequestedImeVisible(lastState.mRequestedImeVisible);
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
if (isForwardNavigation) {
if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV);
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
if (state.hasImeFocusChanged()) {
if (DEBUG) Slog.v(TAG, "Window asks to hide input");
return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
if (isForwardNavigation) {
if (allowVisible) {
if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV);
} else {
Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
+ " there is no focused view that also returns true from"
+ " View#onCheckIsTextEditor()");
}
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
if (DEBUG) Slog.v(TAG, "Window asks to always show input");
if (allowVisible) {
if (state.hasImeFocusChanged()) {
return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE);
}
} else {
Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
+ " there is no focused view that also returns true from"
+ " View#onCheckIsTextEditor()");
}
break;
}
if (!state.hasImeFocusChanged()) {
// On previous platforms, when Dialogs re-gained focus, the Activity behind
// would briefly gain focus first, and dismiss the IME.
// On R that behavior has been fixed, but unfortunately apps have come
// to rely on this behavior to hide the IME when the editor no longer has focus
// To maintain compatibility, we are now hiding the IME when we don't have
// an editor upon refocusing a window.
if (state.isStartInputByGainFocus()) {
if (DEBUG) Slog.v(TAG, "Same window without editor will hide input");
return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
}
}
if (!state.hasEditorFocused() && mInputShown && state.isStartInputByGainFocus()
&& mService.mInputMethodDeviceConfigs.shouldHideImeWhenNoEditorFocus()) {
// Hide the soft-keyboard when the system do nothing for softInputModeState
// of the window being gained focus without an editor. This behavior benefits
// to resolve some unexpected IME visible cases while that window with following
// configurations being switched from an IME shown window:
// 1) SOFT_INPUT_STATE_UNCHANGED state without an editor
// 2) SOFT_INPUT_STATE_VISIBLE state without an editor
// 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor
if (DEBUG) Slog.v(TAG, "Window without editor will hide input");
return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR);
}
return null;
}
@VisibleForTesting
ImeVisibilityResult onInteractiveChanged(IBinder windowToken, boolean interactive) {
final ImeTargetWindowState state = getWindowStateOrNull(windowToken);
if (state != null && state.isRequestedImeVisible() && mInputShown && !interactive) {
mRequestedImeScreenshot = true;
return new ImeVisibilityResult(STATE_SHOW_IME_SNAPSHOT, SHOW_IME_SCREENSHOT_FROM_IMMS);
}
if (interactive && mRequestedImeScreenshot) {
mRequestedImeScreenshot = false;
return new ImeVisibilityResult(STATE_REMOVE_IME_SNAPSHOT,
REMOVE_IME_SCREENSHOT_FROM_IMMS);
}
return null;
}
IBinder getWindowTokenFrom(IBinder requestImeToken) {
for (IBinder windowToken : mRequestWindowStateMap.keySet()) {
final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
if (state.getRequestImeToken() == requestImeToken) {
return windowToken;
}
}
// Fallback to the focused window for some edge cases (e.g. relaunching the activity)
return mService.mCurFocusedWindow;
}
IBinder getWindowTokenFrom(ImeTargetWindowState windowState) {
for (IBinder windowToken : mRequestWindowStateMap.keySet()) {
final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
if (state == windowState) {
return windowToken;
}
}
return null;
}
boolean shouldRestoreImeVisibility(@NonNull ImeTargetWindowState state) {
final int softInputMode = state.getSoftInputModeState();
switch (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
return false;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
if ((softInputMode & SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
return false;
}
}
return mWindowManagerInternal.shouldRestoreImeVisibility(getWindowTokenFrom(state));
}
boolean isInputShown() {
return mInputShown;
}
void setInputShown(boolean inputShown) {
mInputShown = inputShown;
}
void dumpDebug(ProtoOutputStream proto, long fieldId) {
proto.write(SHOW_EXPLICITLY_REQUESTED, mRequestedShowExplicitly);
proto.write(SHOW_FORCED, mShowForced);
proto.write(ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD,
mPolicy.isA11yRequestNoSoftKeyboard());
proto.write(INPUT_SHOWN, mInputShown);
}
void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
final Printer p = new PrintWriterPrinter(pw);
p.println(prefix + "mRequestedShowExplicitly=" + mRequestedShowExplicitly
+ " mShowForced=" + mShowForced);
p.println(prefix + "mImeHiddenByDisplayPolicy=" + mPolicy.isImeHiddenByDisplayPolicy());
p.println(prefix + "mInputShown=" + mInputShown);
}
/**
* A settings class to manage all IME related visibility policies or settings.
*
* This is used for the visibility computer to manage and tell
* {@link InputMethodManagerService} if the requested IME visibility is valid from
* application call or the focus window.
*/
static class ImeVisibilityPolicy {
/**
* {@code true} if the Ime policy has been set to
* {@link WindowManager#DISPLAY_IME_POLICY_HIDE}.
*
* This prevents the IME from showing when it otherwise may have shown.
*/
private boolean mImeHiddenByDisplayPolicy;
/**
* Set when the accessibility service requests to hide IME by
* {@link AccessibilityService.SoftKeyboardController#setShowMode}
*/
private boolean mA11yRequestingNoSoftKeyboard;
/**
* Used when A11y request to hide IME temporary when receiving
* {@link AccessibilityService#SHOW_MODE_HIDDEN} from
* {@link android.provider.Settings.Secure#ACCESSIBILITY_SOFT_KEYBOARD_MODE} without
* changing the requested IME visible state.
*/
private boolean mPendingA11yRequestingHideKeyboard;
void setImeHiddenByDisplayPolicy(boolean hideIme) {
mImeHiddenByDisplayPolicy = hideIme;
}
boolean isImeHiddenByDisplayPolicy() {
return mImeHiddenByDisplayPolicy;
}
void setA11yRequestNoSoftKeyboard(int keyboardShowMode) {
mA11yRequestingNoSoftKeyboard =
(keyboardShowMode & AccessibilityService.SHOW_MODE_MASK) == SHOW_MODE_HIDDEN;
if (mA11yRequestingNoSoftKeyboard) {
mPendingA11yRequestingHideKeyboard = true;
}
}
boolean isA11yRequestNoSoftKeyboard() {
return mA11yRequestingNoSoftKeyboard;
}
}
ImeVisibilityPolicy getImePolicy() {
return mPolicy;
}
/**
* A class that represents the current state of the IME target window.
*/
static class ImeTargetWindowState {
ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags,
boolean imeFocusChanged, boolean hasFocusedEditor,
boolean isStartInputByGainFocus) {
this(softInputModeState, windowFlags, imeFocusChanged, hasFocusedEditor,
isStartInputByGainFocus, TOOL_TYPE_UNKNOWN);
}
ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags,
boolean imeFocusChanged, boolean hasFocusedEditor,
boolean isStartInputByGainFocus, @MotionEvent.ToolType int toolType) {
mSoftInputModeState = softInputModeState;
mWindowFlags = windowFlags;
mImeFocusChanged = imeFocusChanged;
mHasFocusedEditor = hasFocusedEditor;
mIsStartInputByGainFocus = isStartInputByGainFocus;
mToolType = toolType;
}
/**
* Visibility state for this window. By default no state has been specified.
*/
private final @SoftInputModeFlags int mSoftInputModeState;
private final int mWindowFlags;
/**
* {@link MotionEvent#getToolType(int)} that was used to click editor.
*/
private final int mToolType;
/**
* {@code true} means the IME focus changed from the previous window, {@code false}
* otherwise.
*/
private final boolean mImeFocusChanged;
/**
* {@code true} when the window has focused an editor, {@code false} otherwise.
*/
private final boolean mHasFocusedEditor;
private final boolean mIsStartInputByGainFocus;
/**
* Set if the client has asked for the input method to be shown.
*/
private boolean mRequestedImeVisible;
/**
* A identifier for knowing the requester of {@link InputMethodManager#showSoftInput} or
* {@link InputMethodManager#hideSoftInputFromWindow}.
*/
private IBinder mRequestImeToken;
/**
* The IME target display id for which the latest startInput was called.
*/
private int mImeDisplayId = DEFAULT_DISPLAY;
boolean hasImeFocusChanged() {
return mImeFocusChanged;
}
boolean hasEditorFocused() {
return mHasFocusedEditor;
}
boolean isStartInputByGainFocus() {
return mIsStartInputByGainFocus;
}
int getSoftInputModeState() {
return mSoftInputModeState;
}
int getWindowFlags() {
return mWindowFlags;
}
int getToolType() {
return mToolType;
}
private void setImeDisplayId(int imeDisplayId) {
mImeDisplayId = imeDisplayId;
}
int getImeDisplayId() {
return mImeDisplayId;
}
private void setRequestedImeVisible(boolean requestedImeVisible) {
mRequestedImeVisible = requestedImeVisible;
}
boolean isRequestedImeVisible() {
return mRequestedImeVisible;
}
void setRequestImeToken(IBinder token) {
mRequestImeToken = token;
}
IBinder getRequestImeToken() {
return mRequestImeToken;
}
@Override
public String toString() {
return "ImeTargetWindowState{ imeToken " + mRequestImeToken
+ " imeFocusChanged " + mImeFocusChanged
+ " hasEditorFocused " + mHasFocusedEditor
+ " requestedImeVisible " + mRequestedImeVisible
+ " imeDisplayId " + mImeDisplayId
+ " softInputModeState " + softInputModeToString(mSoftInputModeState)
+ " isStartInputByGainFocus " + mIsStartInputByGainFocus
+ "}";
}
}
}