1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wm; 18 19 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; 20 import static android.view.InsetsState.ITYPE_IME; 21 22 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME; 23 import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL; 24 import static com.android.server.wm.DisplayContent.IME_TARGET_INPUT; 25 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; 26 import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME; 27 import static com.android.server.wm.ImeInsetsSourceProviderProto.INSETS_SOURCE_PROVIDER; 28 import static com.android.server.wm.ImeInsetsSourceProviderProto.IS_IME_LAYOUT_DRAWN; 29 import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS; 30 31 import android.annotation.NonNull; 32 import android.os.Trace; 33 import android.util.proto.ProtoOutputStream; 34 import android.view.InsetsSource; 35 import android.view.InsetsSourceControl; 36 import android.view.WindowInsets; 37 import android.window.TaskSnapshot; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.protolog.common.ProtoLog; 41 42 import java.io.PrintWriter; 43 44 /** 45 * Controller for IME inset source on the server. It's called provider as it provides the 46 * {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}. 47 */ 48 final class ImeInsetsSourceProvider extends InsetsSourceProvider { 49 50 private InsetsControlTarget mImeRequester; 51 private Runnable mShowImeRunner; 52 private boolean mIsImeLayoutDrawn; 53 private boolean mImeShowing; 54 private final InsetsSource mLastSource = new InsetsSource(ITYPE_IME); 55 ImeInsetsSourceProvider(InsetsSource source, InsetsStateController stateController, DisplayContent displayContent)56 ImeInsetsSourceProvider(InsetsSource source, 57 InsetsStateController stateController, DisplayContent displayContent) { 58 super(source, stateController, displayContent); 59 } 60 61 @Override getControl(InsetsControlTarget target)62 InsetsSourceControl getControl(InsetsControlTarget target) { 63 final InsetsSourceControl control = super.getControl(target); 64 if (control != null && target != null && target.getWindow() != null) { 65 final WindowState targetWin = target.getWindow(); 66 // If the control target changes during the app transition with the task snapshot 67 // starting window and the IME snapshot is visible, in case not have duplicated IME 68 // showing animation during transitioning, use a flag to inform IME source control to 69 // skip showing animation once. 70 final TaskSnapshot snapshot = targetWin.getRootTask() != null 71 ? targetWin.mWmService.getTaskSnapshot(targetWin.getRootTask().mTaskId, 72 0 /* userId */, false /* isLowResolution */, false /* restoreFromDisk */) 73 : null; 74 control.setSkipAnimationOnce(targetWin.mActivityRecord != null 75 && targetWin.mActivityRecord.hasStartingWindow() 76 && snapshot != null && snapshot.hasImeSurface()); 77 } 78 return control; 79 } 80 81 @Override updateSourceFrame()82 void updateSourceFrame() { 83 super.updateSourceFrame(); 84 onSourceChanged(); 85 } 86 87 @Override updateVisibility()88 protected void updateVisibility() { 89 super.updateVisibility(); 90 onSourceChanged(); 91 } 92 onSourceChanged()93 private void onSourceChanged() { 94 if (mLastSource.equals(mSource)) { 95 return; 96 } 97 mLastSource.set(mSource); 98 mDisplayContent.mWmService.mH.obtainMessage( 99 UPDATE_MULTI_WINDOW_STACKS, mDisplayContent).sendToTarget(); 100 } 101 102 /** 103 * Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService} 104 * requests to show IME on {@param imeTarget}. 105 * 106 * @param imeTarget imeTarget on which IME request is coming from. 107 */ scheduleShowImePostLayout(InsetsControlTarget imeTarget)108 void scheduleShowImePostLayout(InsetsControlTarget imeTarget) { 109 boolean targetChanged = isTargetChangedWithinActivity(imeTarget); 110 mImeRequester = imeTarget; 111 if (targetChanged) { 112 // target changed, check if new target can show IME. 113 ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord"); 114 checkShowImePostLayout(); 115 // if IME cannot be shown at this time, it is scheduled to be shown. 116 // once window that called IMM.showSoftInput() and DisplayContent's ImeTarget match, 117 // it will be shown. 118 return; 119 } 120 121 ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null 122 ? mImeRequester : mImeRequester.getWindow().getName()); 123 mShowImeRunner = () -> { 124 ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner"); 125 // Target should still be the same. 126 if (isReadyToShowIme()) { 127 final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL); 128 129 ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s", 130 target.getWindow() != null ? target.getWindow().getName() : ""); 131 setImeShowing(true); 132 target.showInsets(WindowInsets.Type.ime(), true /* fromIme */); 133 Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); 134 if (target != mImeRequester && mImeRequester != null) { 135 ProtoLog.w(WM_DEBUG_IME, 136 "showInsets(ime) was requested by different window: %s ", 137 (mImeRequester.getWindow() != null 138 ? mImeRequester.getWindow().getName() : "")); 139 } 140 } 141 abortShowImePostLayout(); 142 }; 143 mDisplayContent.mWmService.requestTraversal(); 144 } 145 checkShowImePostLayout()146 void checkShowImePostLayout() { 147 // check if IME is drawn 148 if (mIsImeLayoutDrawn 149 || (isReadyToShowIme() 150 && mWin != null 151 && mWin.isDrawn() 152 && !mWin.mGivenInsetsPending)) { 153 mIsImeLayoutDrawn = true; 154 // show IME if InputMethodService requested it to be shown. 155 if (mShowImeRunner != null) { 156 mShowImeRunner.run(); 157 } 158 } 159 } 160 161 /** 162 * Abort any pending request to show IME post layout. 163 */ abortShowImePostLayout()164 void abortShowImePostLayout() { 165 ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout"); 166 mImeRequester = null; 167 mIsImeLayoutDrawn = false; 168 mShowImeRunner = null; 169 } 170 171 @VisibleForTesting isReadyToShowIme()172 boolean isReadyToShowIme() { 173 // IMMS#mLastImeTargetWindow always considers focused window as 174 // IME target, however DisplayContent#computeImeTarget() can compute 175 // a different IME target. 176 // Refer to WindowManagerService#applyImeVisibility(token, false). 177 // If IMMS's imeTarget is child of DisplayContent's imeTarget and child window 178 // is above the parent, we will consider it as the same target for now. 179 // Also, if imeTarget is closing, it would be considered as outdated target. 180 // TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of 181 // actual IME target. 182 final InsetsControlTarget dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING); 183 if (dcTarget == null || mImeRequester == null) { 184 return false; 185 } 186 ProtoLog.d(WM_DEBUG_IME, "dcTarget: %s mImeRequester: %s", 187 dcTarget.getWindow().getName(), mImeRequester.getWindow() == null 188 ? mImeRequester : mImeRequester.getWindow().getName()); 189 190 return isImeLayeringTarget(mImeRequester, dcTarget) 191 || isAboveImeLayeringTarget(mImeRequester, dcTarget) 192 || isImeFallbackTarget(mImeRequester) 193 || isImeInputTarget(mImeRequester) 194 || sameAsImeControlTarget(); 195 } 196 197 // --------------------------------------------------------------------------------------- 198 // Methods for checking IME insets target changing state. 199 // isImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)200 private static boolean isImeLayeringTarget(@NonNull InsetsControlTarget target, 201 @NonNull InsetsControlTarget dcTarget) { 202 return !dcTarget.getWindow().isClosing() && target == dcTarget; 203 } 204 isAboveImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)205 private static boolean isAboveImeLayeringTarget(@NonNull InsetsControlTarget target, 206 @NonNull InsetsControlTarget dcTarget) { 207 return target.getWindow() != null 208 && dcTarget.getWindow().getParentWindow() == target 209 && dcTarget.getWindow().mSubLayer > target.getWindow().mSubLayer; 210 } 211 isImeFallbackTarget(InsetsControlTarget target)212 private boolean isImeFallbackTarget(InsetsControlTarget target) { 213 return target == mDisplayContent.getImeFallback(); 214 } 215 isImeInputTarget(InsetsControlTarget target)216 private boolean isImeInputTarget(InsetsControlTarget target) { 217 return target == mDisplayContent.getImeTarget(IME_TARGET_INPUT); 218 } 219 sameAsImeControlTarget()220 private boolean sameAsImeControlTarget() { 221 final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL); 222 return target == mImeRequester 223 && (mImeRequester.getWindow() == null 224 || !mImeRequester.getWindow().isClosing()); 225 } 226 isTargetChangedWithinActivity(InsetsControlTarget target)227 private boolean isTargetChangedWithinActivity(InsetsControlTarget target) { 228 // We don't consider the target out of the activity. 229 if (target == null || target.getWindow() == null) { 230 return false; 231 } 232 return mImeRequester != target 233 && mImeRequester != null && mShowImeRunner != null 234 && mImeRequester.getWindow() != null 235 && mImeRequester.getWindow().mActivityRecord 236 == target.getWindow().mActivityRecord; 237 } 238 // --------------------------------------------------------------------------------------- 239 240 @Override dump(PrintWriter pw, String prefix)241 public void dump(PrintWriter pw, String prefix) { 242 super.dump(pw, prefix); 243 prefix = prefix + " "; 244 pw.print(prefix); 245 pw.print("mImeShowing="); 246 pw.print(mImeShowing); 247 if (mImeRequester != null) { 248 pw.print(prefix); 249 pw.print("showImePostLayout pending for mImeRequester="); 250 pw.print(mImeRequester); 251 pw.println(); 252 } 253 pw.println(); 254 } 255 256 @Override dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel)257 void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { 258 final long token = proto.start(fieldId); 259 super.dumpDebug(proto, INSETS_SOURCE_PROVIDER, logLevel); 260 final WindowState imeRequesterWindow = 261 mImeRequester != null ? mImeRequester.getWindow() : null; 262 if (imeRequesterWindow != null) { 263 imeRequesterWindow.dumpDebug(proto, IME_TARGET_FROM_IME, logLevel); 264 } 265 proto.write(IS_IME_LAYOUT_DRAWN, mIsImeLayoutDrawn); 266 proto.end(token); 267 } 268 269 /** 270 * Sets whether the IME is currently supposed to be showing according to 271 * InputMethodManagerService. 272 */ setImeShowing(boolean imeShowing)273 public void setImeShowing(boolean imeShowing) { 274 mImeShowing = imeShowing; 275 } 276 277 /** 278 * Returns whether the IME is currently supposed to be showing according to 279 * InputMethodManagerService. 280 */ isImeShowing()281 public boolean isImeShowing() { 282 return mImeShowing; 283 } 284 } 285