blob: 3e6e06a27fdcc6ef0f100b30f7e32b613185886e [file] [log] [blame]
/*
* Copyright (C) 2019 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.wm;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.InsetsState.ITYPE_IME;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME;
import static com.android.server.wm.ImeInsetsSourceProviderProto.INSETS_SOURCE_PROVIDER;
import static com.android.server.wm.ImeInsetsSourceProviderProto.IS_IME_LAYOUT_DRAWN;
import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.Trace;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.WindowInsets;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import java.io.PrintWriter;
/**
* Controller for IME inset source on the server. It's called provider as it provides the
* {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}.
*/
final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider {
private InsetsControlTarget mImeRequester;
private Runnable mShowImeRunner;
private boolean mIsImeLayoutDrawn;
private boolean mImeShowing;
private final InsetsSource mLastSource = new InsetsSource(ITYPE_IME);
ImeInsetsSourceProvider(InsetsSource source,
InsetsStateController stateController, DisplayContent displayContent) {
super(source, stateController, displayContent);
}
@Override
InsetsSourceControl getControl(InsetsControlTarget target) {
final InsetsSourceControl control = super.getControl(target);
if (control != null && target != null && target.getWindow() != null) {
final WindowState targetWin = target.getWindow();
// If the control target changes during the app transition with the task snapshot
// starting window and the IME snapshot is visible, in case not have duplicated IME
// showing animation during transitioning, use a flag to inform IME source control to
// skip showing animation once.
final TaskSnapshot snapshot = targetWin.getRootTask() != null
? targetWin.mWmService.getTaskSnapshot(targetWin.getRootTask().mTaskId,
0 /* userId */, false /* isLowResolution */, false /* restoreFromDisk */)
: null;
control.setSkipAnimationOnce(targetWin.mActivityRecord != null
&& targetWin.mActivityRecord.hasStartingWindow()
&& snapshot != null && snapshot.hasImeSurface());
}
return control;
}
@Override
void updateSourceFrame(Rect frame) {
super.updateSourceFrame(frame);
onSourceChanged();
}
@Override
protected void updateVisibility() {
super.updateVisibility();
onSourceChanged();
}
@Override
void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
if (target != null && target.getWindow() != null) {
// ime control target could be a different window.
// Refer WindowState#getImeControlTarget().
target = target.getWindow().getImeControlTarget();
}
super.updateControlForTarget(target, force);
}
@Override
protected boolean updateClientVisibility(InsetsControlTarget caller) {
boolean changed = super.updateClientVisibility(caller);
if (changed && caller.getRequestedVisibility(mSource.getType())) {
reportImeDrawnForOrganizer(caller);
}
return changed;
}
private void reportImeDrawnForOrganizer(InsetsControlTarget caller) {
if (caller.getWindow() != null && caller.getWindow().getTask() != null) {
if (caller.getWindow().getTask().isOrganized()) {
mWindowContainer.mWmService.mAtmService.mTaskOrganizerController
.reportImeDrawnOnTask(caller.getWindow().getTask());
}
}
}
private void onSourceChanged() {
if (mLastSource.equals(mSource)) {
return;
}
mLastSource.set(mSource);
mDisplayContent.mWmService.mH.obtainMessage(
UPDATE_MULTI_WINDOW_STACKS, mDisplayContent).sendToTarget();
}
/**
* Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService}
* requests to show IME on {@param imeTarget}.
*
* @param imeTarget imeTarget on which IME request is coming from.
*/
void scheduleShowImePostLayout(InsetsControlTarget imeTarget) {
boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
mImeRequester = imeTarget;
if (targetChanged) {
// target changed, check if new target can show IME.
ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord");
checkShowImePostLayout();
// if IME cannot be shown at this time, it is scheduled to be shown.
// once window that called IMM.showSoftInput() and DisplayContent's ImeTarget match,
// it will be shown.
return;
}
ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null
? mImeRequester : mImeRequester.getWindow().getName());
mShowImeRunner = () -> {
ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
// Target should still be the same.
if (isReadyToShowIme()) {
final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s",
target.getWindow() != null ? target.getWindow().getName() : "");
setImeShowing(true);
target.showInsets(WindowInsets.Type.ime(), true /* fromIme */);
Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
if (target != mImeRequester && mImeRequester != null) {
ProtoLog.w(WM_DEBUG_IME,
"showInsets(ime) was requested by different window: %s ",
(mImeRequester.getWindow() != null
? mImeRequester.getWindow().getName() : ""));
}
}
abortShowImePostLayout();
};
mDisplayContent.mWmService.requestTraversal();
}
void checkShowImePostLayout() {
if (mWindowContainer == null) {
return;
}
WindowState windowState = mWindowContainer.asWindowState();
if (windowState == null) {
throw new IllegalArgumentException("IME insets must be provided by a window.");
}
// check if IME is drawn
if (mIsImeLayoutDrawn
|| (isReadyToShowIme()
&& windowState.isDrawn()
&& !windowState.mGivenInsetsPending)) {
mIsImeLayoutDrawn = true;
// show IME if InputMethodService requested it to be shown.
if (mShowImeRunner != null) {
mShowImeRunner.run();
}
}
}
/**
* Abort any pending request to show IME post layout.
*/
void abortShowImePostLayout() {
ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout");
mImeRequester = null;
mIsImeLayoutDrawn = false;
mShowImeRunner = null;
}
@VisibleForTesting
boolean isReadyToShowIme() {
// IMMS#mLastImeTargetWindow always considers focused window as
// IME target, however DisplayContent#computeImeTarget() can compute
// a different IME target.
// Refer to WindowManagerService#applyImeVisibility(token, false).
// If IMMS's imeTarget is child of DisplayContent's imeTarget and child window
// is above the parent, we will consider it as the same target for now.
// Also, if imeTarget is closing, it would be considered as outdated target.
// TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of
// actual IME target.
final InsetsControlTarget dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
if (dcTarget == null || mImeRequester == null) {
return false;
}
ProtoLog.d(WM_DEBUG_IME, "dcTarget: %s mImeRequester: %s",
dcTarget.getWindow().getName(), mImeRequester.getWindow() == null
? mImeRequester : mImeRequester.getWindow().getName());
return isImeLayeringTarget(mImeRequester, dcTarget)
|| isAboveImeLayeringTarget(mImeRequester, dcTarget)
|| isImeFallbackTarget(mImeRequester)
|| isImeInputTarget(mImeRequester)
|| sameAsImeControlTarget();
}
// ---------------------------------------------------------------------------------------
// Methods for checking IME insets target changing state.
//
private static boolean isImeLayeringTarget(@NonNull InsetsControlTarget target,
@NonNull InsetsControlTarget dcTarget) {
return !isImeTargetWindowClosing(dcTarget.getWindow()) && target == dcTarget;
}
private static boolean isAboveImeLayeringTarget(@NonNull InsetsControlTarget target,
@NonNull InsetsControlTarget dcTarget) {
return target.getWindow() != null
&& dcTarget.getWindow().getParentWindow() == target
&& dcTarget.getWindow().mSubLayer > target.getWindow().mSubLayer;
}
private boolean isImeFallbackTarget(InsetsControlTarget target) {
return target == mDisplayContent.getImeFallback();
}
private boolean isImeInputTarget(InsetsControlTarget target) {
return target == mDisplayContent.getImeInputTarget();
}
private boolean sameAsImeControlTarget() {
final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
return target == mImeRequester
&& (mImeRequester.getWindow() == null
|| !isImeTargetWindowClosing(mImeRequester.getWindow()));
}
private static boolean isImeTargetWindowClosing(@NonNull WindowState win) {
return win.mAnimatingExit || win.mActivityRecord != null
&& (win.mActivityRecord.isInTransition()
&& !win.mActivityRecord.isVisibleRequested()
|| win.mActivityRecord.willCloseOrEnterPip());
}
private boolean isTargetChangedWithinActivity(InsetsControlTarget target) {
// We don't consider the target out of the activity.
if (target == null || target.getWindow() == null) {
return false;
}
return mImeRequester != target
&& mImeRequester != null && mShowImeRunner != null
&& mImeRequester.getWindow() != null
&& mImeRequester.getWindow().mActivityRecord
== target.getWindow().mActivityRecord;
}
// ---------------------------------------------------------------------------------------
@Override
public void dump(PrintWriter pw, String prefix) {
super.dump(pw, prefix);
prefix = prefix + " ";
pw.print(prefix);
pw.print("mImeShowing=");
pw.print(mImeShowing);
if (mImeRequester != null) {
pw.print(prefix);
pw.print("showImePostLayout pending for mImeRequester=");
pw.print(mImeRequester);
pw.println();
}
pw.println();
}
@Override
void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) {
final long token = proto.start(fieldId);
super.dumpDebug(proto, INSETS_SOURCE_PROVIDER, logLevel);
final WindowState imeRequesterWindow =
mImeRequester != null ? mImeRequester.getWindow() : null;
if (imeRequesterWindow != null) {
imeRequesterWindow.dumpDebug(proto, IME_TARGET_FROM_IME, logLevel);
}
proto.write(IS_IME_LAYOUT_DRAWN, mIsImeLayoutDrawn);
proto.end(token);
}
/**
* Sets whether the IME is currently supposed to be showing according to
* InputMethodManagerService.
*/
public void setImeShowing(boolean imeShowing) {
mImeShowing = imeShowing;
}
/**
* Returns whether the IME is currently supposed to be showing according to
* InputMethodManagerService.
*/
public boolean isImeShowing() {
return mImeShowing;
}
}