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.InsetsSource.ID_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.InsetsSourceConsumer; 37 import android.view.InsetsSourceControl; 38 import android.view.WindowInsets; 39 import android.view.inputmethod.ImeTracker; 40 import android.window.TaskSnapshot; 41 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.protolog.common.ProtoLog; 44 45 import java.io.PrintWriter; 46 47 /** 48 * Controller for IME inset source on the server. It's called provider as it provides the 49 * {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}. 50 */ 51 final class ImeInsetsSourceProvider extends InsetsSourceProvider { 52 53 /** The token tracking the current IME request or {@code null} otherwise. */ 54 @Nullable 55 private ImeTracker.Token mImeRequesterStatsToken; 56 private InsetsControlTarget mImeRequester; 57 private Runnable mShowImeRunner; 58 private boolean mIsImeLayoutDrawn; 59 private boolean mImeShowing; 60 private final InsetsSource mLastSource = new InsetsSource(ID_IME, WindowInsets.Type.ime()); 61 62 /** @see #setFrozen(boolean) */ 63 private boolean mFrozen; 64 65 /** @see #setServerVisible(boolean) */ 66 private boolean mServerVisible; 67 ImeInsetsSourceProvider(InsetsSource source, InsetsStateController stateController, DisplayContent displayContent)68 ImeInsetsSourceProvider(InsetsSource source, 69 InsetsStateController stateController, DisplayContent displayContent) { 70 super(source, stateController, displayContent); 71 } 72 73 @Override getControl(InsetsControlTarget target)74 InsetsSourceControl getControl(InsetsControlTarget target) { 75 final InsetsSourceControl control = super.getControl(target); 76 if (control != null && target != null && target.getWindow() != null) { 77 final WindowState targetWin = target.getWindow(); 78 // If the control target changes during the app transition with the task snapshot 79 // starting window and the IME snapshot is visible, in case not have duplicated IME 80 // showing animation during transitioning, use a flag to inform IME source control to 81 // skip showing animation once. 82 final TaskSnapshot snapshot = targetWin.getRootTask() != null 83 ? targetWin.mWmService.getTaskSnapshot(targetWin.getRootTask().mTaskId, 84 0 /* userId */, false /* isLowResolution */, false /* restoreFromDisk */) 85 : null; 86 control.setSkipAnimationOnce(targetWin.mActivityRecord != null 87 && targetWin.mActivityRecord.hasStartingWindow() 88 && snapshot != null && snapshot.hasImeSurface()); 89 } 90 return control; 91 } 92 93 @Override setClientVisible(boolean clientVisible)94 void setClientVisible(boolean clientVisible) { 95 final boolean wasClientVisible = isClientVisible(); 96 super.setClientVisible(clientVisible); 97 // The layer of ImePlaceholder needs to be updated on a higher z-order for 98 // non-activity window (For activity window, IME is already on top of it). 99 if (!wasClientVisible && isClientVisible()) { 100 final InsetsControlTarget imeControlTarget = getControlTarget(); 101 if (imeControlTarget != null && imeControlTarget.getWindow() != null 102 && imeControlTarget.getWindow().mActivityRecord == null) { 103 mDisplayContent.assignWindowLayers(false /* setLayoutNeeded */); 104 } 105 } 106 } 107 108 @Override setServerVisible(boolean serverVisible)109 void setServerVisible(boolean serverVisible) { 110 mServerVisible = serverVisible; 111 if (!mFrozen) { 112 super.setServerVisible(serverVisible); 113 } 114 } 115 116 /** 117 * Freeze IME insets source state when required. 118 * 119 * When setting {@param frozen} as {@code true}, the IME insets provider will freeze the 120 * current IME insets state and pending the IME insets state update until setting 121 * {@param frozen} as {@code false}. 122 */ setFrozen(boolean frozen)123 void setFrozen(boolean frozen) { 124 if (mFrozen == frozen) { 125 return; 126 } 127 mFrozen = frozen; 128 if (!frozen) { 129 // Unfreeze and process the pending IME insets states. 130 super.setServerVisible(mServerVisible); 131 } 132 } 133 134 @Override updateSourceFrame(Rect frame)135 void updateSourceFrame(Rect frame) { 136 super.updateSourceFrame(frame); 137 onSourceChanged(); 138 } 139 140 @Override updateVisibility()141 protected void updateVisibility() { 142 super.updateVisibility(); 143 onSourceChanged(); 144 } 145 146 @Override updateControlForTarget(@ullable InsetsControlTarget target, boolean force)147 void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) { 148 if (target != null && target.getWindow() != null) { 149 // ime control target could be a different window. 150 // Refer WindowState#getImeControlTarget(). 151 target = target.getWindow().getImeControlTarget(); 152 } 153 super.updateControlForTarget(target, force); 154 } 155 156 @Override updateClientVisibility(InsetsControlTarget caller)157 protected boolean updateClientVisibility(InsetsControlTarget caller) { 158 if (caller != getControlTarget()) { 159 return false; 160 } 161 boolean changed = super.updateClientVisibility(caller); 162 if (changed && caller.isRequestedVisible(mSource.getType())) { 163 reportImeDrawnForOrganizerIfNeeded(caller); 164 } 165 changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate(); 166 return changed; 167 } 168 reportImeDrawnForOrganizerIfNeeded(@onNull InsetsControlTarget caller)169 private void reportImeDrawnForOrganizerIfNeeded(@NonNull InsetsControlTarget caller) { 170 final WindowState callerWindow = caller.getWindow(); 171 if (callerWindow == null) { 172 return; 173 } 174 WindowToken imeToken = mWindowContainer.asWindowState() != null 175 ? mWindowContainer.asWindowState().mToken : null; 176 if (mDisplayContent.getAsyncRotationController() != null 177 && mDisplayContent.getAsyncRotationController().isTargetToken(imeToken)) { 178 // Skip reporting IME drawn state when the control target is in fixed 179 // rotation, AsyncRotationController will report after the animation finished. 180 return; 181 } 182 reportImeDrawnForOrganizer(caller); 183 } 184 reportImeDrawnForOrganizer(@onNull InsetsControlTarget caller)185 private void reportImeDrawnForOrganizer(@NonNull InsetsControlTarget caller) { 186 final WindowState callerWindow = caller.getWindow(); 187 if (callerWindow == null || callerWindow.getTask() == null) { 188 return; 189 } 190 if (callerWindow.getTask().isOrganized()) { 191 mWindowContainer.mWmService.mAtmService.mTaskOrganizerController 192 .reportImeDrawnOnTask(caller.getWindow().getTask()); 193 } 194 } 195 196 /** Report the IME has drawn on the current IME control target for its task organizer */ reportImeDrawnForOrganizer()197 void reportImeDrawnForOrganizer() { 198 final InsetsControlTarget imeControlTarget = getControlTarget(); 199 if (imeControlTarget != null) { 200 reportImeDrawnForOrganizer(imeControlTarget); 201 } 202 } 203 onSourceChanged()204 private void onSourceChanged() { 205 if (mLastSource.equals(mSource)) { 206 return; 207 } 208 mLastSource.set(mSource); 209 mDisplayContent.mWmService.mH.obtainMessage( 210 UPDATE_MULTI_WINDOW_STACKS, mDisplayContent).sendToTarget(); 211 } 212 213 /** 214 * Called from {@link WindowManagerInternal#showImePostLayout} 215 * when {@link android.inputmethodservice.InputMethodService} requests to show IME 216 * on {@param imeTarget}. 217 * 218 * @param imeTarget imeTarget on which IME show request is coming from. 219 * @param statsToken the token tracking the current IME show request or {@code null} otherwise. 220 */ scheduleShowImePostLayout(InsetsControlTarget imeTarget, @Nullable ImeTracker.Token statsToken)221 void scheduleShowImePostLayout(InsetsControlTarget imeTarget, 222 @Nullable ImeTracker.Token statsToken) { 223 boolean targetChanged = isTargetChangedWithinActivity(imeTarget); 224 mImeRequester = imeTarget; 225 // There was still a stats token, so that request presumably failed. 226 ImeTracker.forLogging().onFailed( 227 mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER); 228 mImeRequesterStatsToken = statsToken; 229 if (targetChanged) { 230 // target changed, check if new target can show IME. 231 ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord"); 232 checkShowImePostLayout(); 233 // if IME cannot be shown at this time, it is scheduled to be shown. 234 // once window that called IMM.showSoftInput() and DisplayContent's ImeTarget match, 235 // it will be shown. 236 return; 237 } 238 239 ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null 240 ? mImeRequester : mImeRequester.getWindow().getName()); 241 mShowImeRunner = () -> { 242 ImeTracker.forLogging().onProgress(mImeRequesterStatsToken, 243 ImeTracker.PHASE_WM_SHOW_IME_RUNNER); 244 ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner"); 245 // Target should still be the same. 246 if (isReadyToShowIme()) { 247 ImeTracker.forLogging().onProgress(mImeRequesterStatsToken, 248 ImeTracker.PHASE_WM_SHOW_IME_READY); 249 final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL); 250 251 ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s", 252 target.getWindow() != null ? target.getWindow().getName() : ""); 253 setImeShowing(true); 254 target.showInsets(WindowInsets.Type.ime(), true /* fromIme */, 255 mImeRequesterStatsToken); 256 Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); 257 if (target != mImeRequester && mImeRequester != null) { 258 ProtoLog.w(WM_DEBUG_IME, 259 "showInsets(ime) was requested by different window: %s ", 260 (mImeRequester.getWindow() != null 261 ? mImeRequester.getWindow().getName() : "")); 262 } 263 } else { 264 ImeTracker.forLogging().onFailed(mImeRequesterStatsToken, 265 ImeTracker.PHASE_WM_SHOW_IME_READY); 266 } 267 // Clear token here so we don't report an error in abortShowImePostLayout(). 268 mImeRequesterStatsToken = null; 269 abortShowImePostLayout(); 270 }; 271 mDisplayContent.mWmService.requestTraversal(); 272 } 273 checkShowImePostLayout()274 void checkShowImePostLayout() { 275 if (mWindowContainer == null) { 276 return; 277 } 278 WindowState windowState = mWindowContainer.asWindowState(); 279 if (windowState == null) { 280 throw new IllegalArgumentException("IME insets must be provided by a window."); 281 } 282 // check if IME is drawn 283 if (mIsImeLayoutDrawn 284 || (isReadyToShowIme() 285 && windowState.isDrawn() 286 && !windowState.mGivenInsetsPending)) { 287 mIsImeLayoutDrawn = true; 288 // show IME if InputMethodService requested it to be shown. 289 if (mShowImeRunner != null) { 290 mShowImeRunner.run(); 291 } 292 } 293 } 294 295 /** 296 * Abort any pending request to show IME post layout. 297 */ abortShowImePostLayout()298 void abortShowImePostLayout() { 299 ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout"); 300 mImeRequester = null; 301 mIsImeLayoutDrawn = false; 302 mShowImeRunner = null; 303 ImeTracker.forLogging().onCancelled( 304 mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER); 305 mImeRequesterStatsToken = null; 306 } 307 308 @VisibleForTesting isReadyToShowIme()309 boolean isReadyToShowIme() { 310 // IMMS#mLastImeTargetWindow always considers focused window as 311 // IME target, however DisplayContent#computeImeTarget() can compute 312 // a different IME target. 313 // Refer to WindowManagerService#applyImeVisibility(token, false). 314 // If IMMS's imeTarget is child of DisplayContent's imeTarget and child window 315 // is above the parent, we will consider it as the same target for now. 316 // Also, if imeTarget is closing, it would be considered as outdated target. 317 // TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of 318 // actual IME target. 319 final InsetsControlTarget dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING); 320 if (dcTarget == null || mImeRequester == null) { 321 return false; 322 } 323 // Not ready to show if there is no IME control target. 324 final InsetsControlTarget controlTarget = mDisplayContent.getImeTarget(IME_TARGET_CONTROL); 325 if (controlTarget == null) { 326 return false; 327 } 328 329 ProtoLog.d(WM_DEBUG_IME, "dcTarget: %s mImeRequester: %s", 330 dcTarget.getWindow().getName(), mImeRequester.getWindow() == null 331 ? mImeRequester : mImeRequester.getWindow().getName()); 332 333 return isImeLayeringTarget(mImeRequester, dcTarget) 334 || isAboveImeLayeringTarget(mImeRequester, dcTarget) 335 || isImeFallbackTarget(mImeRequester) 336 || isImeInputTarget(mImeRequester) 337 || sameAsImeControlTarget(); 338 } 339 340 // --------------------------------------------------------------------------------------- 341 // Methods for checking IME insets target changing state. 342 // isImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)343 private static boolean isImeLayeringTarget(@NonNull InsetsControlTarget target, 344 @NonNull InsetsControlTarget dcTarget) { 345 return !isImeTargetWindowClosing(dcTarget.getWindow()) && target == dcTarget; 346 } 347 isAboveImeLayeringTarget(@onNull InsetsControlTarget target, @NonNull InsetsControlTarget dcTarget)348 private static boolean isAboveImeLayeringTarget(@NonNull InsetsControlTarget target, 349 @NonNull InsetsControlTarget dcTarget) { 350 return target.getWindow() != null 351 && dcTarget.getWindow().getParentWindow() == target 352 && dcTarget.getWindow().mSubLayer > target.getWindow().mSubLayer; 353 } 354 isImeFallbackTarget(InsetsControlTarget target)355 private boolean isImeFallbackTarget(InsetsControlTarget target) { 356 return target == mDisplayContent.getImeFallback(); 357 } 358 isImeInputTarget(InsetsControlTarget target)359 private boolean isImeInputTarget(InsetsControlTarget target) { 360 return target == mDisplayContent.getImeInputTarget(); 361 } 362 sameAsImeControlTarget()363 private boolean sameAsImeControlTarget() { 364 final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL); 365 return target == mImeRequester 366 && (mImeRequester.getWindow() == null 367 || !isImeTargetWindowClosing(mImeRequester.getWindow())); 368 } 369 isImeTargetWindowClosing(@onNull WindowState win)370 private static boolean isImeTargetWindowClosing(@NonNull WindowState win) { 371 return win.mAnimatingExit || win.mActivityRecord != null 372 && (win.mActivityRecord.isInTransition() 373 && !win.mActivityRecord.isVisibleRequested() 374 || win.mActivityRecord.willCloseOrEnterPip()); 375 } 376 isTargetChangedWithinActivity(InsetsControlTarget target)377 private boolean isTargetChangedWithinActivity(InsetsControlTarget target) { 378 // We don't consider the target out of the activity. 379 if (target == null || target.getWindow() == null) { 380 return false; 381 } 382 return mImeRequester != target 383 && mImeRequester != null && mShowImeRunner != null 384 && mImeRequester.getWindow() != null 385 && mImeRequester.getWindow().mActivityRecord 386 == target.getWindow().mActivityRecord; 387 } 388 // --------------------------------------------------------------------------------------- 389 390 @Override dump(PrintWriter pw, String prefix)391 public void dump(PrintWriter pw, String prefix) { 392 super.dump(pw, prefix); 393 prefix = prefix + " "; 394 pw.print(prefix); 395 pw.print("mImeShowing="); 396 pw.print(mImeShowing); 397 if (mImeRequester != null) { 398 pw.print(prefix); 399 pw.print("showImePostLayout pending for mImeRequester="); 400 pw.print(mImeRequester); 401 pw.println(); 402 } 403 pw.println(); 404 } 405 406 @Override dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel)407 void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { 408 final long token = proto.start(fieldId); 409 super.dumpDebug(proto, INSETS_SOURCE_PROVIDER, logLevel); 410 final WindowState imeRequesterWindow = 411 mImeRequester != null ? mImeRequester.getWindow() : null; 412 if (imeRequesterWindow != null) { 413 imeRequesterWindow.dumpDebug(proto, IME_TARGET_FROM_IME, logLevel); 414 } 415 proto.write(IS_IME_LAYOUT_DRAWN, mIsImeLayoutDrawn); 416 proto.end(token); 417 } 418 419 /** 420 * Sets whether the IME is currently supposed to be showing according to 421 * InputMethodManagerService. 422 */ setImeShowing(boolean imeShowing)423 public void setImeShowing(boolean imeShowing) { 424 mImeShowing = imeShowing; 425 } 426 427 /** 428 * Returns whether the IME is currently supposed to be showing according to 429 * InputMethodManagerService. 430 */ isImeShowing()431 public boolean isImeShowing() { 432 return mImeShowing; 433 } 434 } 435