blob: f1698dd0f0256e933ba0d65d1231d2eb7d567525 [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.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
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.EventLogTags.IMF_HIDE_IME;
import static com.android.server.EventLogTags.IMF_SHOW_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_REMOVE_IME_SNAPSHOT;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_SNAPSHOT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.util.EventLog;
import android.util.Slog;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.server.LocalServices;
import com.android.server.wm.ImeTargetVisibilityPolicy;
import com.android.server.wm.WindowManagerInternal;
import java.util.Objects;
/**
* The default implementation of {@link ImeVisibilityApplier} used in
* {@link InputMethodManagerService}.
*/
final class DefaultImeVisibilityApplier implements ImeVisibilityApplier {
private static final String TAG = "DefaultImeVisibilityApplier";
private static final boolean DEBUG = InputMethodManagerService.DEBUG;
private InputMethodManagerService mService;
private final WindowManagerInternal mWindowManagerInternal;
@NonNull
private final ImeTargetVisibilityPolicy mImeTargetVisibilityPolicy;
DefaultImeVisibilityApplier(InputMethodManagerService service) {
mService = service;
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mImeTargetVisibilityPolicy = LocalServices.getService(ImeTargetVisibilityPolicy.class);
}
@GuardedBy("ImfLock.class")
@Override
public void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
@InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
if (curMethod != null) {
if (DEBUG) {
Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
+ ", " + showFlags + ", " + resultReceiver + ") for reason: "
+ InputMethodDebug.softInputDisplayReasonToString(reason));
}
// TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) {
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_SHOW_IME, statsToken.getTag(),
Objects.toString(mService.mCurFocusedWindow),
InputMethodDebug.softInputDisplayReasonToString(reason),
InputMethodDebug.softInputModeToString(
mService.mCurFocusedWindowSoftInputMode));
}
mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason,
statsToken);
}
}
}
@GuardedBy("ImfLock.class")
@Override
public void performHideIme(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
if (curMethod != null) {
// The IME will report its visible state again after the following message finally
// delivered to the IME process as an IPC. Hence the inconsistency between
// IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
// the final state.
if (DEBUG) {
Slog.v(TAG, "Calling " + curMethod + ".hideSoftInput(0, " + hideInputToken
+ ", " + resultReceiver + ") for reason: "
+ InputMethodDebug.softInputDisplayReasonToString(reason));
}
// TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
if (curMethod.hideSoftInput(hideInputToken, statsToken, 0, resultReceiver)) {
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_HIDE_IME, statsToken.getTag(),
Objects.toString(mService.mCurFocusedWindow),
InputMethodDebug.softInputDisplayReasonToString(reason),
InputMethodDebug.softInputModeToString(
mService.mCurFocusedWindowSoftInputMode));
}
mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason,
statsToken);
}
}
}
@GuardedBy("ImfLock.class")
@Override
public void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
@ImeVisibilityStateComputer.VisibilityState int state) {
applyImeVisibility(windowToken, statsToken, state, -1 /* ignore reason */);
}
@GuardedBy("ImfLock.class")
void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
@ImeVisibilityStateComputer.VisibilityState int state, int reason) {
switch (state) {
case STATE_SHOW_IME:
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
// Send to window manager to show IME after IME layout finishes.
mWindowManagerInternal.showImePostLayout(windowToken, statsToken);
break;
case STATE_HIDE_IME:
if (mService.hasAttachedClient()) {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
// IMMS only knows of focused window, not the actual IME target.
// e.g. it isn't aware of any window that has both
// NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target.
// Send it to window manager to hide IME from the actual IME control target
// of the target display.
mWindowManagerInternal.hideIme(windowToken,
mService.getDisplayIdToShowImeLocked(), statsToken);
} else {
ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
}
break;
case STATE_HIDE_IME_EXPLICIT:
mService.hideCurrentInputLocked(windowToken, statsToken, 0, null, reason);
break;
case STATE_HIDE_IME_NOT_ALWAYS:
mService.hideCurrentInputLocked(windowToken, statsToken,
InputMethodManager.HIDE_NOT_ALWAYS, null, reason);
break;
case STATE_SHOW_IME_IMPLICIT:
mService.showCurrentInputLocked(windowToken, statsToken,
InputMethodManager.SHOW_IMPLICIT, null, reason);
break;
case STATE_SHOW_IME_SNAPSHOT:
showImeScreenshot(windowToken, mService.getDisplayIdToShowImeLocked(), null);
break;
case STATE_REMOVE_IME_SNAPSHOT:
removeImeScreenshot(mService.getDisplayIdToShowImeLocked());
break;
default:
throw new IllegalArgumentException("Invalid IME visibility state: " + state);
}
}
@GuardedBy("ImfLock.class")
@Override
public boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId,
@Nullable ImeTracker.Token statsToken) {
if (mImeTargetVisibilityPolicy.showImeScreenshot(imeTarget, displayId)) {
mService.onShowHideSoftInputRequested(false /* show */, imeTarget,
SHOW_IME_SCREENSHOT_FROM_IMMS, statsToken);
return true;
}
return false;
}
@GuardedBy("ImfLock.class")
@Override
public boolean removeImeScreenshot(int displayId) {
if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) {
mService.onShowHideSoftInputRequested(false /* show */, mService.mCurFocusedWindow,
REMOVE_IME_SCREENSHOT_FROM_IMMS, null);
return true;
}
return false;
}
}