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_LAYERING; 25 import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME; 26 import static com.android.server.wm.ImeInsetsSourceProviderProto.INSETS_SOURCE_PROVIDER; 27 import static com.android.server.wm.ImeInsetsSourceProviderProto.IS_IME_LAYOUT_DRAWN; 28 import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS; 29 30 import android.annotation.NonNull; 31 import android.annotation.Nullable; 32 import android.graphics.Rect; 33 import android.os.Trace; 34 import android.util.proto.ProtoOutputStream; 35 import android.view.InsetsSource; 36 import android.view.InsetsSourceControl; 37 import android.view.WindowInsets; 38 import android.window.TaskSnapshot; 39 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.protolog.common.ProtoLog; 42 43 import java.io.PrintWriter; 44 45 /** 46 * Controller for IME inset source on the server. It's called provider as it provides the 47 * {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}. 48 */ 49 final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider { 50 51 private InsetsControlTarget mImeRequester; 52 private Runnable mShowImeRunner; 53 private boolean mIsImeLayoutDrawn; 54 private boolean mImeShowing; 55 private final InsetsSource mLastSource = new InsetsSource(ITYPE_IME); 56 ImeInsetsSourceProvider(InsetsSource source, InsetsStateController stateController, DisplayContent displayContent)57 ImeInsetsSourceProvider(InsetsSource source, 58 InsetsStateController stateController, DisplayContent displayContent) { 59 super(source, stateController, displayContent); 60 } 61 62 @Override getControl(InsetsControlTarget target)63 InsetsSourceControl getControl(InsetsControlTarget target) { 64 final InsetsSourceControl control = super.getControl(target); 65 if (control != null && target != null && target.getWindow() != null) { 66 final WindowState targetWin = target.getWindow(); 67 // If the control target changes during the app transition with the task snapshot 68 // starting window and the IME snapshot is visible, in case not have duplicated IME 69 // showing animation during transitioning, use a flag to inform IME source control to 70 // skip showing animation once. 71 final TaskSnapshot snapshot = targetWin.getRootTask() != null 72 ? targetWin.mWmService.getTaskSnapshot(targetWin.getRootTask().mTaskId, 73 0 /* userId */, false /* isLowResolution */, false /* restoreFromDisk */) 74 : null; 75 control.setSkipAnimationOnce(targetWin.mActivityRecord != null 76 && targetWin.mActivityRecord.hasStartingWindow() 77 && snapshot != null && snapshot.hasImeSurface()); 78 } 79 return control; 80 } 81 82 @Override updateSourceFrame(Rect frame)83 void updateSourceFrame(Rect frame) { 84 super.updateSourceFrame(frame); 85 onSourceChanged(); 86 } 87 88 @Override updateVisibility()89 protected void updateVisibility() { 90 super.updateVisibility(); 91 onSourceChanged(); 92 } 93 94 @Override updateControlForTarget(@ullable InsetsControlTarget target, boolean force)95 void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) { 96 if (target != null && target.getWindow() != null) { 97 // ime control target could be a different window. 98 // Refer WindowState#getImeControlTarget(). 99 target = target.getWindow().getImeControlTarget(); 100 } 101 super.updateControlForTarget(target, force); 102 } 103 104 @Override updateClientVisibility(InsetsControlTarget caller)105 protected boolean updateClientVisibility(InsetsControlTarget caller) { 106 boolean changed = super.updateClientVisibility(caller); 107 if (changed && caller.getRequestedVisibility(mSource.getType())) { 108 reportImeDrawnForOrganizer(caller); 109 } 110 return changed; 111 } 112 reportImeDrawnForOrganizer(InsetsControlTarget caller)113 private void reportImeDrawnForOrganizer(InsetsControlTarget caller) { 114 if (caller.getWindow() != null && caller.getWindow().getTask() != null) { 115 if (caller.getWindow().getTask().isOrganized()) { 116 mWindowContainer.mWmService.mAtmService.mTaskOrganizerController 117 .reportImeDrawnOnTask(caller.getWindow().getTask()); 118 } 119 } 120 } 121 onSourceChanged()122 private void onSourceChanged() { 123 if (mLastSource.equals(mSource)) { 124 return; 125 } 126 mLastSource.set(mSource); 127 mDisplayContent.mWmService.mH.obtainMessage( 128 UPDATE_MULTI_WINDOW_STACKS, mDisplayContent).sendToTarget(); 129 } 130 131 /** 132 * Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService} 133 * requests to show IME on {@param imeTarget}. 134 * 135 * @param imeTarget imeTarget on which IME request is coming from. 136 */ scheduleShowImePostLayout(InsetsControlTarget imeTarget)137 void scheduleShowImePostLayout(InsetsControlTarget imeTarget) { 138 boolean targetChanged = isTargetChangedWithinActivity(imeTarget); 139 mImeRequester = imeTarget; 140 if (targetChanged) { 141 // target changed, check if new target can show IME. 142 ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord"); 143 checkShowImePostLayout(); 144 // if IME cannot be shown at this time, it is scheduled to be shown. 145 // once window that called IMM.showSoftInput() and DisplayContent's ImeTarget match, 146 // it will be shown. 147 return; 148 } 149 150 ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null 151 ? mImeRequester : mImeRequester.getWindow().getName()); 152 mShowImeRunner = () -> { 153 ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner"); 154 // Target should still be the same. 155 if (isReadyToShowIme()) { 156 final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL); 157 158 ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s", 159 target.getWindow() != null ? target.getWindow().getName() : ""); 160 setImeShowing(true); 161 target.showInsets(WindowInsets.Type.ime(), true /* fromIme */); 162 Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); 163 if (target != mImeRequester && mImeRequester != null) { 164 ProtoLog.w(WM_DEBUG_IME, 165 "showInsets(ime) was requested by different window: %s ", 166 (mImeRequester.getWindow() != null 167 ? mImeRequester.getWindow().getName() : "")); 168 } 169 } 170 abortShowImePostLayout(); 171 }; 172 mDisplayContent.mWmService.requestTraversal(); 173 } 174 checkShowImePostLayout()175 void checkShowImePostLayout() { 176 if (mWindowContainer == null) { 177 return; 178 } 179 WindowState windowState = mWindowContainer.asWindowState(); 180 if (windowState == null) { 181 throw new IllegalArgumentException("IME insets must be provided by a window."); 182 } 183 // check if IME is drawn 184 if (mIsImeLayoutDrawn 185 || (isReadyToShowIme() 186 && windowState.isDrawn() 187 && !windowState.mGivenInsetsPending)) { 188 mIsImeLayoutDrawn = true; 189 // show IME if InputMethodService requested it to be shown. 190 if (mShowImeRunner != null) { 191 mShowImeRunner.run(); 192 } 193 } 194 } 195 196 /** 197 * Abort any pending request to show IME post layout. 198 */ abortShowImePostLayout()199 void abortShowImePostLayout() { 200 ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout"); 201 mImeRequester = null; 202 mIsImeLayoutDrawn = false; 203 mShowImeRunner = null; 204 } 205 206 @VisibleForTesting isReadyToShowIme()207 boolean isReadyToShowIme() { 208 // IMMS#mLastImeTargetWindow always considers focused window as 209 // IME target, however DisplayContent#computeImeTarget() can compute 210 // a different IME target. 211 // Refer to WindowManagerService#applyImeVisibility(token, false). 212 // If IMMS's imeTarget is child of DisplayContent's imeTarget and child window 213 // is above the parent, we will consider it as the same target for now. 214 // Also, if imeTarget is closing, it would be considered as outdated target. 215 // TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of 216 // actual IME target. 217 final InsetsControlTarget dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING); 218 if (dcTarget == null || mImeRequester == null) { 219 return false; 220 } 221 ProtoLog.d(WM_DEBUG_IME, "dcTarget: %s mImeRequester: %s", 222 dcTarget.getWindow().getName(), mImeRequester.getWindow() == null 223 ? mImeRequester : mImeRequester.getWindow().getName()); 224 225 return isImeLayeringTarget(mImeRequester, dcTarget) 226 || isAboveImeLayeringTarget(mImeRequester, dcTarget) 227 || isImeFallbackTarget(mImeRequester) 228 || isImeInputTarget(mImeRequester) 229 || sameAsImeControlTarget(); 230 } 231 232 // --------------------------------------------------------------------------------------- 233 // Methods for checking IME insets target changing state. 234 // isImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)235 private static boolean isImeLayeringTarget(@NonNull InsetsControlTarget target, 236 @NonNull InsetsControlTarget dcTarget) { 237 return !isImeTargetWindowClosing(dcTarget.getWindow()) && target == dcTarget; 238 } 239 isAboveImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)240 private static boolean isAboveImeLayeringTarget(@NonNull InsetsControlTarget target, 241 @NonNull InsetsControlTarget dcTarget) { 242 return target.getWindow() != null 243 && dcTarget.getWindow().getParentWindow() == target 244 && dcTarget.getWindow().mSubLayer > target.getWindow().mSubLayer; 245 } 246 isImeFallbackTarget(InsetsControlTarget target)247 private boolean isImeFallbackTarget(InsetsControlTarget target) { 248 return target == mDisplayContent.getImeFallback(); 249 } 250 isImeInputTarget(InsetsControlTarget target)251 private boolean isImeInputTarget(InsetsControlTarget target) { 252 return target == mDisplayContent.getImeInputTarget(); 253 } 254 sameAsImeControlTarget()255 private boolean sameAsImeControlTarget() { 256 final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL); 257 return target == mImeRequester 258 && (mImeRequester.getWindow() == null 259 || !isImeTargetWindowClosing(mImeRequester.getWindow())); 260 } 261 isImeTargetWindowClosing(@onNull WindowState win)262 private static boolean isImeTargetWindowClosing(@NonNull WindowState win) { 263 return win.mAnimatingExit || win.mActivityRecord != null 264 && (win.mActivityRecord.isInTransition() 265 && !win.mActivityRecord.isVisibleRequested() 266 || win.mActivityRecord.willCloseOrEnterPip()); 267 } 268 isTargetChangedWithinActivity(InsetsControlTarget target)269 private boolean isTargetChangedWithinActivity(InsetsControlTarget target) { 270 // We don't consider the target out of the activity. 271 if (target == null || target.getWindow() == null) { 272 return false; 273 } 274 return mImeRequester != target 275 && mImeRequester != null && mShowImeRunner != null 276 && mImeRequester.getWindow() != null 277 && mImeRequester.getWindow().mActivityRecord 278 == target.getWindow().mActivityRecord; 279 } 280 // --------------------------------------------------------------------------------------- 281 282 @Override dump(PrintWriter pw, String prefix)283 public void dump(PrintWriter pw, String prefix) { 284 super.dump(pw, prefix); 285 prefix = prefix + " "; 286 pw.print(prefix); 287 pw.print("mImeShowing="); 288 pw.print(mImeShowing); 289 if (mImeRequester != null) { 290 pw.print(prefix); 291 pw.print("showImePostLayout pending for mImeRequester="); 292 pw.print(mImeRequester); 293 pw.println(); 294 } 295 pw.println(); 296 } 297 298 @Override dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel)299 void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { 300 final long token = proto.start(fieldId); 301 super.dumpDebug(proto, INSETS_SOURCE_PROVIDER, logLevel); 302 final WindowState imeRequesterWindow = 303 mImeRequester != null ? mImeRequester.getWindow() : null; 304 if (imeRequesterWindow != null) { 305 imeRequesterWindow.dumpDebug(proto, IME_TARGET_FROM_IME, logLevel); 306 } 307 proto.write(IS_IME_LAYOUT_DRAWN, mIsImeLayoutDrawn); 308 proto.end(token); 309 } 310 311 /** 312 * Sets whether the IME is currently supposed to be showing according to 313 * InputMethodManagerService. 314 */ setImeShowing(boolean imeShowing)315 public void setImeShowing(boolean imeShowing) { 316 mImeShowing = imeShowing; 317 } 318 319 /** 320 * Returns whether the IME is currently supposed to be showing according to 321 * InputMethodManagerService. 322 */ isImeShowing()323 public boolean isImeShowing() { 324 return mImeShowing; 325 } 326 } 327